【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;
	}
}
posted @ 2022-10-30 10:40  ez_lcw  阅读(19)  评论(0编辑  收藏  举报