K-D Tree学习笔记
引入
K-D Tree 是一種處理高維空間的數據結構。
支持O(nk?1k)O(n^{\frac {k-1}k})O(nkk?1?)查詢給定超矩形內的點的信息, kkk 為維數。
可以用替罪羊樹的思想實現動態插入。
但其實用的最多的是在錯誤的復雜度內查詢奇奇怪怪的點信息。
構建
K-D Tree 是平衡樹的結構(但應該不能叫平衡樹)。
它的核心思想是通過樹一層層的分化,把整個空間分割成若干子空間。
但是點是kkk維的,而樹只有二維,所以一次我們只能選擇一維來劃分。
我們關心的問題是如何選取進行比較的維度。
一種方法是選取方差最大的維度,但在OI中不常用
OI中一般輪流選取,即深度為ddd的結點按第d%kd\%kd%k維的坐標劃分
具體而言,設點的序列為ppp,對于一個需要構建區間 [L,R][L,R][L,R],設當前需要分的維度為ddd
如果 L=RL=RL=R ,那么劃分出的只有當前一個點
為了保證盡量均勻,我們選取這一維的中位數作為當前子樹的根
設 mid=(l+r)/2
可以使用 C++ STL 中一個奧妙重重的函數 nth_element
它可以把第 kkk 小的元素放在第 kkk 位,然后比它小的放在左邊(但并不保證有序,后同),比它大的放在右邊。復雜度為 O(n)O(n)O(n) (其實就是閹割版的快排)
這樣我們 nth_element(p+l,p+mid,p+r+1) 就完事了
因為已經幫你分好了 ,所以把中位數間個點當根,然后遞歸 [L,mid?1][L,mid-1][L,mid?1] 和 [mid+1,R][mid+1,R][mid+1,R]
最后更新子樹信息,我們要維護的是超矩形,記錄一下每一維的最小值和最大值就可以了。
復雜度 O(nlog?n)O(n\log n)O(nlogn)
插入
用平衡樹的方法插入,根據當前層數判斷往左還是往右
當然可能會插成鏈,你又不能旋轉,所以只能考慮重構
暴力推平重新建就可以了,沒啥技術含量
只是重建帶一個 log?\loglog ,所以可能跑得有點慢……
詢問
詢問一個 kkk 維超矩形內所有點的信息
用線段樹的思想無腦遞歸就可以了
如果完全包含當前點劃分出的子空間直接返回當前子樹的值,沒有相交返回 000
復雜度可以證明是O(n1?1k)O(n^{1-\frac 1k})O(n1?k1?)
對于 k=2k=2k=2 的情況提供一個不知道對不對的證明:
對于一個結點,設其子樹大小為nnn,我們考慮往下分兩層
設T1(n)T_1(n)T1?(n)表示詢問中間挖一塊的復雜度
T2(n)T_2(n)T2?(n)表示挖一個角的復雜度
T3(n)T_3(n)T3?(n)為平行挖的復雜度
顯然有
T1(n)=4T2(n4)T2(n)=2T3(n4)+T2(n4)T3(n)=2T3(n4)T_1(n)=4T_2(\frac n4)\\T_2(n)=2T_3(\frac n4)+T_2(\frac n4)\\T_3(n)=2T_3(\frac n4)T1?(n)=4T2?(4n?)T2?(n)=2T3?(4n?)+T2?(4n?)T3?(n)=2T3?(4n?)
解得
T1(n)=T2(n)=T3(n)=nT_1(n)=T_2(n)=T_3(n)=\sqrt nT1?(n)=T2?(n)=T3?(n)=n?
有一些變種,比如一次函數的下方、最近點對,但這些復雜度都是錯的。
不過如果出題人不刻意卡跑得還是挺快的,是個騙分的不錯選擇(
例題
Luogu4148 簡單題
有一個n×nn\times nn×n的方陣,開始時值都為000,維護mmm次操作:
n≤5×105,m≤2×105n\leq5\times10^5,m\leq2\times10^5n≤5×105,m≤2×105
強制在線,空間限制20M
模板題
#include <iostream> #include <cstdio> #include <cstring> #include <cctype> #include <algorithm> #define MAXN 200005 using namespace std; inline int read() {int ans=0;char c=getchar();while (!isdigit(c)) c=getchar();while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();return ans; } const double alpha=0.7; struct point{int x[2];point(const int& a=0,const int& b=0){x[0]=a,x[1]=b;}}; inline point min(const point& a,const point& b){return point(min(a.x[0],b.x[0]),min(a.x[1],b.x[1]));} inline point max(const point& a,const point& b){return point(max(a.x[0],b.x[0]),max(a.x[1],b.x[1]));} inline bool operator ==(const point& a,const point& b){return a.x[0]==b.x[0]&&a.x[1]==b.x[1];} int val[MAXN],sum[MAXN],siz[MAXN],ch[MAXN][2],tot; point cur[MAXN],L[MAXN],R[MAXN]; int rt,*tag,dir; int rub[MAXN]; inline int newnode(const int& v,const point& p) {int x=rub[0]? rub[rub[0]--]:++tot;val[x]=sum[x]=v,siz[x]=1,cur[x]=L[x]=R[x]=p;ch[x][0]=ch[x][1]=0;return x; } inline void update(const int& x) {sum[x]=val[x]+sum[ch[x][0]]+sum[ch[x][1]];siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+1;L[x]=min(cur[x],min(L[ch[x][0]],L[ch[x][1]]));R[x]=max(cur[x],max(R[ch[x][0]],R[ch[x][1]])); } void insert(int& x,int k,int v,const point& p) {if (!x) return (void)(x=newnode(v,p));if (cur[x]==p) return val[x]+=v,update(x);int d=p.x[k]<cur[x].x[k];insert(ch[x][d],k^1,v,p);update(x);if (siz[ch[x][d]]>siz[x]*alpha) tag=&x,dir=k; } struct node{int v;point p;}; node buf[MAXN]; int cnt; int cur_k; inline bool operator <(const node& a,const node& b){return a.p.x[cur_k]<b.p.x[cur_k];} void build(int& x,int k,int l,int r) {if (l>r) return (void)(x=0);int mid=(l+r)>>1;cur_k=k,nth_element(buf+l,buf+mid,buf+r+1);x=newnode(buf[mid].v,buf[mid].p);build(ch[x][0],k^1,l,mid-1),build(ch[x][1],k^1,mid+1,r);update(x); } void dfs(int x) {if (!x) return;dfs(ch[x][0]);buf[++cnt].p=cur[x],buf[cnt].v=val[x],rub[++rub[0]]=x;dfs(ch[x][1]); } inline void rebuild() {cnt=0;dfs(*tag);build(*tag,dir,1,cnt); } int query(int x,point l,point r) {if (l.x[0]<=L[x].x[0]&&R[x].x[0]<=r.x[0]&&l.x[1]<=L[x].x[1]&&R[x].x[1]<=r.x[1]) return sum[x];if (r.x[0]<L[x].x[0]||R[x].x[0]<l.x[0]||r.x[1]<L[x].x[1]||R[x].x[1]<l.x[1]) return 0;int ans=0;if (l.x[0]<=cur[x].x[0]&&cur[x].x[0]<=r.x[0]&&l.x[1]<=cur[x].x[1]&&cur[x].x[1]<=r.x[1]) ans=val[x];return ans+query(ch[x][0],l,r)+query(ch[x][1],l,r); } int main() {memset(L,0x7f,sizeof(L));read();int lans=0;while (true){int x1,y1,x2,y2,v;switch(read()){case 1:x1=read()^lans,y1=read()^lans,v=read()^lans;tag=NULL;insert(rt,0,v,point(x1,y1));if (tag!=NULL) rebuild();break;case 2:x1=read()^lans,y1=read()^lans,x2=read()^lans,y2=read()^lans;printf("%d\n",lans=query(rt,point(x1,y1),point(x2,y2)));break;case 3:return 0;}} }Luogu4475 巧克力王國
題意:給定nnn對xi,yix_i,y_ixi?,yi?,mmm次詢問,每次給定a,b,ca,b,ca,b,c,求ax+by<cax+by<cax+by<c的(x,y)(x,y)(x,y)的個數
n,m≤5×104,?109≤x,y,a,b,c≤109n,m\leq 5\times 10^4,-10^9\leq x,y,a,b,c\leq10^9n,m≤5×104,?109≤x,y,a,b,c≤109
先口胡一份
顯然滿足條件的(x,y)(x,y)(x,y)在一個一次函數下方
建出K-D Tree后,詢問時因為是矩形,如果四個點都在這個函數下方,那么整個矩形中的點都在下方,直接返回
注意這個復雜度是錯誤的,只是數據沒卡跑得快
總結
以上是生活随笔為你收集整理的K-D Tree学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【BZOJ3328】PYXFIB【矩阵快
- 下一篇: 【ZJOI2015】幻想乡战略游戏【点分