好久以前我就想过怎么快速在二维平面上查找一个区域的信息,思考许久无果,只能想到几种优秀一点的暴力。php
KD树就是干上面那件事的。node
别的很少说,赶忙把本身的理解写下来,省得凉了。ios
以维护k维空间(x,y,……)内的KD树为例,主要由一下三部分组成:数组
不看mi和mx,长得就和splay/trie树同样,一个p维护当前节点,一个ch[2]记录左右儿子。数据结构
不看p[k],长得就和线段树同样,有左右儿子和区间信息。ide
没错,KD树功能如线段树,结点维护区域信息;形态如splay/trie树,每一个结点有实际的值和意义。函数
通常题目都是二维平面。下面就以二维平面KD树的构建为例。post
读入把点存进结构体数组a中,坐标分别为a[x].p[i]。大数据
inline void build(int &x,int l,int r,int type){ x=(l+r)>>1;now=type; nth_element(a+l,a+x,a+r+1,cmp); nd=a[x];newnode(x); if(l<x)build(ch[x][0],l,x-1,type^1);else ch[x][0]=0; if(x<r)build(ch[x][1],x+1,r,type^1);else ch[x][1]=0; pushup(x); } build(kd.root,1,n,0);
很是优美……对type、now做用不明的同窗请继续阅读……你要如今就明白就奇怪了ui
系统函数nth_element(a+l,a+x,a+r+1),头文件algorithm,需定义<或cmp函数。
做用:把排序后第x大的放到第x位,比它小的放进左边,比它大的放进右边(两边无序)。
注意区间开闭:左闭右开,中间也是闭合的。
复杂度:平均,指望是O(n)?能够接受。
下面给出cmp、newnode、pushup代码。
struct Node{int p[2],mi[2],mx[2];}a[N]; inline bool cmp(const Node &a,const Node &b){return a.p[now]<b.p[now];} inline void Min(int &x,int y){x=x<y?x:y;} inline void Max(int &x,int y){x=x>y?x:y;} inline void pushup(int x){ int ls=ch[x][0],rs=ch[x][1]; if(ls){ Min(T[x].mi[0],T[ls].mi[0]);Max(T[x].mx[0],T[ls].mx[0]); Min(T[x].mi[1],T[ls].mi[1]);Max(T[x].mx[1],T[ls].mx[1]); } if(rs){ Min(T[x].mi[0],T[rs].mi[0]);Max(T[x].mx[0],T[rs].mx[0]); Min(T[x].mi[1],T[rs].mi[1]);Max(T[x].mx[1],T[rs].mx[1]); } } inline void newnode(int x){ T[x].p[0]=T[x].mi[0]=T[x].mx[0]=nd.p[0]; T[x].p[1]=T[x].mi[1]=T[x].mx[1]=nd.p[1]; }
不要问我为何辣么长,为了减常冲榜,把循环展开了……
聪明的读者已经发现KD树的构建巧妙之处。它不是纯粹按照x维,或者某一维排序,而是先按x维,再按y维,再按z维,再……最后又回到x维……
这样分割的区域更加整齐划一更加均匀紧缩,不像上面的按照某一维划分,到最后变成一条条长条,KD树划分到底的图案仍是很好看的。
这样分割有什么好处呢?等你真正领悟了KD树的精髓以后你就会发现……嘿嘿嘿……
(就是为了把这个暴力数据结构剪枝剪更多跑更快)
1.往KD树上插点
插点能够分为插新点和插老点。若是有老点,特判一句,把信息覆盖便可。
inline void insert(int &x,int type){ if(!x){x=++cnt,newnode(cnt);return;} if(nd.p[0]==T[x].p[0] && nd.p[1]==T[x].p[1]){ ……(自行维护);return; } if(nd.p[type]<T[x].p[type])insert(ch[x][0],type^1); else insert(ch[x][1],type^1); pushup(x); }
依然很是的美妙……等等有什么不对?
咱们能估计出一棵刚建好的KD树深度是O(log)的。
但你这么随便乱插……有道题叫HNOI2017 spaly 插入不旋转的单旋spaly见过?T成苟。
这都不是问题!知不知道有一种数据结构叫作替罪羊树哇?
知道替罪羊树怎么保证复杂度的吗?
重构!大力重构!自信重构!不爽就重构!
为了省事大概每插入10000次就重构一次好了……
if(kd.cnt==sz){ for(int i=1;i<=sz;++i)a[i]=kd.T[i]; kd.rebuild(kd.root,1,sz,0);sz+=10000; }
2.在KD树上查询
//查询的点(x',y')储存在nd中。 //这里的l,r就是mi,mx的意思。 inline int dis(Node p,int x,int ans=0){ for(int i=0;i<2;++i) ans+=max(0,t[x].l[i]-p.p[i])+max(0,p.p[i]-t[x].r[i]); return ans; } inline void query(int x){ Ans=min(Ans,abs(t[x].p[0]-nd.p[0])+abs(t[x].p[1]-nd.p[1])); int dl=ch[x][0]?dis(nd,ch[x][0]):Inf; int dr=ch[x][1]?dis(nd,ch[x][1]):Inf; if(dl<dr){ if(dl<Ans)query(ch[x][0]); if(dr<Ans)query(ch[x][1]); } else{ if(dr<Ans)query(ch[x][1]); if(dl<Ans)query(ch[x][0]); } }
inline bool in(int l,int r,int xl,int xr){return l<=xl && xr<=r;} inline bool out(int l,int r,int xl,int xr){return xr<l || r<xl;} inline int query(int x,int x1,int y1,int x2,int y2){ int ans=0;if(!x)return ans; if(in(x1,x2,T[x].mi[0],T[x].mx[0])) if(in(y1,y2,T[x].mi[1],T[x].mx[1])) return T[x].sum; if(out(x1,x2,T[x].mi[0],T[x].mx[0]))return 0; if(out(y1,y2,T[x].mi[1],T[x].mx[1]))return 0; if(in(x1,x2,T[x].p[0],T[x].p[0])) if(in(y1,y2,T[x].p[1],T[x].p[1])) ans+=T[x].val; return ans+query(ch[x][0],x1,y1,x2,y2)+query(ch[x][1],x1,y1,x2,y2); }
KD树的基本姿式大概就是这个样子……好写很差写错,基本上都是个板子。
附上学长的一(三)句话,从各方面进行了深度总结:
“能不写最好仍是不要写吧,轻松被卡→_→,也许能够出奇制胜?若是要写,从新构树是个不错的选择。发现大数据跑不过,多半是剪枝挂了。”
仍是给个连接……MashiroSky大爷。
upd:以当前坐标差最大的来作type应该比轮换type更优秀……
例题有"SJY摆棋子"、"简单题"等,在此就不作赘述了。
比较有意思的应用就是【bzoj3489】 A simple rmq problem,正如上面所言,KD树解决传统数据结构题出奇制胜。
附上"BZOJ4066简单题"代码一份,操做是单点修改+矩形求和在线。
#include <iostream> #include <cstdio> #include <cstdlib> #include <algorithm> #include <vector> #include <cstring> #include <queue> #include <complex> #include <stack> #define LL long long int #define dob double #define FILE "bzoj_4066" //#define FILE "简单题" using namespace std; const int N = 200010; int n,lst,now,sz=10000; inline int gi(){ int x=0,res=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();} while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar(); return x*res; } inline void Min(int &x,int y){x=x<y?x:y;} inline void Max(int &x,int y){x=x>y?x:y;} struct Node{int p[2],mi[2],mx[2],val,sum;}a[N]; inline bool cmp(const Node &a,const Node &b){return a.p[now]<b.p[now];} struct KD_Tree{ int ch[N][2],root,cnt; Node T[N],nd; inline void pushup(int x){ int ls=ch[x][0],rs=ch[x][1]; if(ls){ Min(T[x].mi[0],T[ls].mi[0]);Max(T[x].mx[0],T[ls].mx[0]); Min(T[x].mi[1],T[ls].mi[1]);Max(T[x].mx[1],T[ls].mx[1]); } if(rs){ Min(T[x].mi[0],T[rs].mi[0]);Max(T[x].mx[0],T[rs].mx[0]); Min(T[x].mi[1],T[rs].mi[1]);Max(T[x].mx[1],T[rs].mx[1]); } T[x].sum=T[x].val; if(ls)T[x].sum+=T[ls].sum; if(rs)T[x].sum+=T[rs].sum; } inline void newnode(int x){ T[x].p[0]=T[x].mi[0]=T[x].mx[0]=nd.p[0]; T[x].p[1]=T[x].mi[1]=T[x].mx[1]=nd.p[1]; T[x].sum=T[x].val=nd.val; } inline void insert(int &x,int type){ if(!x){x=++cnt,newnode(cnt);return;} if(nd.p[0]==T[x].p[0] && nd.p[1]==T[x].p[1]){ T[x].val+=nd.val;T[x].sum+=nd.val; return; } if(nd.p[type]<T[x].p[type])insert(ch[x][0],type^1); else insert(ch[x][1],type^1); pushup(x); } inline void rebuild(int &x,int l,int r,int type){ x=(l+r)>>1;now=type; nth_element(a+l,a+x,a+r+1,cmp); nd=a[x];newnode(x); if(l<x)rebuild(ch[x][0],l,x-1,type^1);else ch[x][0]=0; if(x<r)rebuild(ch[x][1],x+1,r,type^1);else ch[x][1]=0; pushup(x); } inline bool in(int l,int r,int xl,int xr){return l<=xl && xr<=r;} inline bool out(int l,int r,int xl,int xr){return xr<l || r<xl;} inline int query(int x,int x1,int y1,int x2,int y2){ int ans=0;if(!x)return ans; if(in(x1,x2,T[x].mi[0],T[x].mx[0])) if(in(y1,y2,T[x].mi[1],T[x].mx[1])) return T[x].sum; if(out(x1,x2,T[x].mi[0],T[x].mx[0]))return 0; if(out(y1,y2,T[x].mi[1],T[x].mx[1]))return 0; if(in(x1,x2,T[x].p[0],T[x].p[0])) if(in(y1,y2,T[x].p[1],T[x].p[1])) ans+=T[x].val; return ans+query(ch[x][0],x1,y1,x2,y2)+query(ch[x][1],x1,y1,x2,y2); } }kd; int main() { freopen(FILE".in","r",stdin); freopen(FILE".out","w",stdout); n=gi(); while(1){ int type=gi();if(type==3)break; int x1=gi()^lst,y1=gi()^lst; if(type==1){ int A=gi()^lst; kd.nd.p[0]=x1;kd.nd.p[1]=y1; kd.nd.sum=kd.nd.val=A; kd.insert(kd.root,0); if(kd.cnt==sz){ for(int i=1;i<=sz;++i)a[i]=kd.T[i]; kd.rebuild(kd.root,1,sz,0);sz+=10000; } } if(type==2){ int x2=gi()^lst,y2=gi()^lst; lst=kd.query(kd.root,x1,y1,x2,y2); printf("%d\n",lst); } } fclose(stdin);fclose(stdout); return 0; }