【XSY2630】【BZOJ4066】简单题(kd-tree)
看到题面,第一眼想到的是用cdq或树状数组维护这东东。
但是强制在线限制了我们的想象
然后想到用树套树,毕竟时间和空间好像是\(O(n\log^2n)\)的。
但是20M的空间限制了我们的想象
那么我们就会用到\(kd-tree\)来维护。
1.\(kd-tree\)是什么
\(kd-tree\)其实是一颗平衡树(替罪羊树),它维护了\(k\)维空间的\(n\)个点的信息。
2.如何将一个点插入\(kd-tree\)
我们对于深度为\(i\)的位置,我们按照\(i\%k\)维的坐标来比较,然后按普通平衡树的插入即可。
比如,不妨设这个点是\(A(x_0,x_1,...,x_{k-1})\),我们插入时第一个要比较的节点是\(u=root\),那么如果\(x_0\leq u.x_0\)(\(u.x_0\)即为\(u\)这个点的第\(0\)维的坐标),那么我们跳到\(u\)的左子树去,即\(u=ch[u][0]\)。然后在让\(x_1\)和当前\(u\)的\(x_1\)比较,如此推类,直到找到一个空节点截止。
如果是建树也同理,在深度为\(i\)的位置,我们按照\(i\%k\)维的坐标比较,找出当前点集中的点的第\(i\%k\)维的坐标中在最中间的这一个点。通俗的来说,就是将当前点集按第\(i\%k\)维的坐标排一遍序,然后取出最中间的点,把它当成当前子树的根,再向左右儿子遍历。
如何更好地理解上述算法的正确性或过程?
看图:(这里以2维为例)
假设现在坐标系里有这么一些点:
我们按照刚才的方法,先按第一维进行比较,得出最中间的点\(A\)。
那么我们把\(A\)当作当前树的根,把\(B\)、\(F\)、\(C\)扔进左子树,把\(D\)、\(G\)、\(I\)扔进右子树。
几何意义上就是做一个以\(A\)为中心点在第一维的分割:
同理,在进入下一层的建树时,我们按第2维来比较。
几何意义:
然后第三层,我们按第一维比较:
这样下来就分割完了,然后建出来的树长这样:
那么在比如我们要插入一个点\(J(3.5,4.5)\)
我们先让\(J\)的横坐标与当前点\(A\)的横坐标比较,发现\(x_J>x_A\),所以继续递归\(A\)的右子树\(G\)。
然后再让\(J\)的纵坐标与当前点\(G\)的纵坐标比较,发现\(y_J<y_G\),所以继续递归\(G\)的左子树\(D\)。
然后再让\(J\)的横坐标与当前点\(D\)的横坐标比较,发现\(x_J<x_D\),所以继续递归\(D\)的左子树。
发现当前节点是空节点,我们就把当前节点设为\(J\)。
然后树就变成了这样:
然后记得如果某棵树不平衡,就暴力重构。
这就是整个\(kd-tree\)插入和建树的全过程。
至于查询,由于在几何意义上,我们每棵子树都代表的是一个矩形,所以我们需要维护这个矩形的\(min_x\)、\(max_x\)、\(min_y\)、\(max_y\),也就是矩形的左边界,右边界,上边界和下边界。
然后我们就可以用这些信息搞事情了。
比如对于这道题,如果你当前子树维护的矩形完全在询问的矩形,就直接返回当前矩形内所有点的权值和就好了,否则就继续递归。
完整代码和注释如下:
#include<bits/stdc++.h>
#define N 500010
#define M 200010
#define lc t[u].ch[0]
#define rc t[u].ch[1]
using namespace std;
struct Point
{
int num[2],val;//num[0]即x,num[1]即y
Point(){};
Point(int xx,int yy,int vall)
{
num[0]=xx,num[1]=yy,val=vall;
}
}p[M];
struct kd_tree
{
int ch[2],minn[2],maxn[2],sum,size;//minn[0]即minx,minn[1]即miny,maxn[0]即maxx,maxn[1]即maxy
Point p;
}t[M];
const double alpha=0.75;
int fuck;
int n,tot,root;
int cnt,rubbish[M];
bool cmp0(Point a,Point b)
{
return a.num[0]<b.num[0];
}
bool cmp1(Point a,Point b)
{
return a.num[1]<b.num[1];
}
int newnode()
{
if(cnt)return rubbish[cnt--];
return ++tot;
}
void up(int u)
{
for(int i=0;i<2;i++)
{
t[u].minn[i]=t[u].maxn[i]=t[u].p.num[i];
if(lc)
{
t[u].minn[i]=min(t[u].minn[i],t[lc].minn[i]);
t[u].maxn[i]=max(t[u].maxn[i],t[lc].maxn[i]);
}
if(rc)
{
t[u].minn[i]=min(t[u].minn[i],t[rc].minn[i]);
t[u].maxn[i]=max(t[u].maxn[i],t[rc].maxn[i]);
}
}
t[u].sum=t[lc].sum+t[u].p.val+t[rc].sum;
t[u].size=t[lc].size+t[rc].size+1;
}
void slap(int u)//把所有的点扔进一个数组里
{
if(!u) return;
p[++fuck]=t[u].p;
rubbish[++cnt]=u;
slap(lc);
slap(rc);
}
int rebuild(int l,int r,int d)//暴力重构
{
if(l>r) return 0;
int mid=(l+r)>>1,u=newnode();
nth_element(p+l,p+mid,p+r+1,d?cmp1:cmp0);
t[u].p=p[mid];
lc=rebuild(l,mid-1,d^1);
rc=rebuild(mid+1,r,d^1);
up(u);
return u;
}
void check(int &u,int d)//检查是否不平衡
{
if(t[lc].size>alpha*t[u].size||t[rc].size>alpha*t[u].size)
{
fuck=0;
slap(u);
u=rebuild(1,t[u].size,d);
}
}
void insert(int &u,Point now,int d)
{
if(!u)
{
u=newnode();
lc=rc=0,t[u].p=now;
up(u);
return;
}
if(now.num[d]<=t[u].p.num[d]) insert(lc,now,d^1);//按照第d维的坐标比较
else insert(rc,now,d^1);
up(u);
check(u,d);
}
bool inside(int x1,int y1,int x2,int y2,int X1,int Y1,int X2,int Y2)
{
return x1<=X1&&x2>=X2&&y1<=Y1&&y2>=Y2;
}
bool outside(int x1,int y1,int x2,int y2,int X1,int Y1,int X2,int Y2)
{
return x1>X2||x2<X1||y1>Y2||y2<Y1;
}
int query(int u,int x1,int y1,int x2,int y2)
{
if(!u) return 0;
if(inside(x1,y1,x2,y2,t[u].minn[0],t[u].minn[1],t[u].maxn[0],t[u].maxn[1])) //判断当前矩形是否完全在询问矩形内
return t[u].sum;
if(outside(x1,y1,x2,y2,t[u].minn[0],t[u].minn[1],t[u].maxn[0],t[u].maxn[1])) //判断当前矩形是否完全在询问矩形外
return 0;
int ans=0;
if(inside(x1,y1,x2,y2,t[u].p.num[0],t[u].p.num[1],t[u].p.num[0],t[u].p.num[1])) //如果根在询问矩形内
ans+=t[u].p.val;
ans+=query(lc,x1,y1,x2,y2)+query(rc,x1,y1,x2,y2);//统计左右子树的答案
return ans;
}
int main()
{
scanf("%d",&n);
int opt,lastans=0;
while(scanf("%d",&opt))
{
if(opt==1)
{
int x,y,val;
scanf("%d%d%d",&x,&y,&val);
x^=lastans,y^=lastans,val^=lastans;
insert(root,Point(x,y,val),0);
}
if(opt==2)
{
int x1,y1,x2,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
x1^=lastans,y1^=lastans,x2^=lastans,y2^=lastans;
printf("%d\n",lastans=query(root,x1,y1,x2,y2));
}
if(opt==3) break;
}
}