【XSY2630】【BZOJ4066】简单题(kd-tree)

看到题面,第一眼想到的是用cdq或树状数组维护这东东。

但是强制在线限制了我们的想象

然后想到用树套树,毕竟时间和空间好像是 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)的。

但是20M的空间限制了我们的想象

那么我们就会用到 k d − t r e e kd-tree kdtree来维护。

1. k d − t r e e kd-tree kdtree是什么

k d − t r e e kd-tree kdtree其实是一颗平衡树(替罪羊树),它维护了 k k k维空间的 n n n个点的信息。

2.如何将一个点插入 k d − t r e e kd-tree kdtree

我们对于深度为 i i i的位置,我们按照 i % k i\%k i%k维的坐标来比较,然后按普通平衡树的插入即可。

比如,不妨设这个点是 A ( x 0 , x 1 , . . . , x k − 1 ) A(x_0,x_1,...,x_{k-1}) A(x0,x1,...,xk1),我们插入时第一个要比较的节点是 u = r o o t u=root u=root,那么如果 x 0 ≤ u . x 0 x_0\leq u.x_0 x0u.x0 u . x 0 u.x_0 u.x0即为 u u u这个点的第 0 0 0维的坐标),那么我们跳到 u u u的左子树去,即 u = c h [ u ] [ 0 ] u=ch[u][0] u=ch[u][0]。然后在让 x 1 x_1 x1和当前 u u u x 1 x_1 x1比较,如此推类,直到找到一个空节点截止。

如果是建树也同理,在深度为 i i i的位置,我们按照 i % k i\%k i%k维的坐标比较,找出当前点集中的点的第 i % k i\%k i%k维的坐标中在最中间的这一个点。通俗的来说,就是将当前点集按第 i % k i\%k i%k维的坐标排一遍序,然后取出最中间的点,把它当成当前子树的根,再向左右儿子遍历。

如何更好地理解上述算法的正确性或过程?

看图:(这里以2维为例)

假设现在坐标系里有这么一些点:

在这里插入图片描述

我们按照刚才的方法,先按第一维进行比较,得出最中间的点 A A A

那么我们把 A A A当作当前树的根,把 B B B F F F C C C扔进左子树,把 D D D G G G I I I扔进右子树。

几何意义上就是做一个以 A A A为中心点在第一维的分割:

在这里插入图片描述

同理,在进入下一层的建树时,我们按第2维来比较。

几何意义:

在这里插入图片描述

然后第三层,我们按第一维比较:

在这里插入图片描述

这样下来就分割完了,然后建出来的树长这样:

在这里插入图片描述

那么在比如我们要插入一个点 J ( 3.5 , 4.5 ) J(3.5,4.5) J(3.5,4.5)

我们先让 J J J的横坐标与当前点 A A A的横坐标比较,发现 x J > x A x_J>x_A xJ>xA,所以继续递归 A A A的右子树 G G G

然后再让 J J J的纵坐标与当前点 G G G的纵坐标比较,发现 y J < y G y_J<y_G yJ<yG,所以继续递归 G G G的左子树 D D D

然后再让 J J J的横坐标与当前点 D D D的横坐标比较,发现 x J < x D x_J<x_D xJ<xD,所以继续递归 D D D的左子树。

发现当前节点是空节点,我们就把当前节点设为 J J J

然后树就变成了这样:

在这里插入图片描述

然后记得如果某棵树不平衡,就暴力重构。

这就是整个 k d − t r e e kd-tree kdtree插入和建树的全过程。

至于查询,由于在几何意义上,我们每棵子树都代表的是一个矩形,所以我们需要维护这个矩形的 m i n x min_x minx m a x x max_x maxx m i n y min_y miny m a x y max_y maxy,也就是矩形的左边界,右边界,上边界和下边界。

然后我们就可以用这些信息搞事情了。

比如对于这道题,如果你当前子树维护的矩形完全在询问的矩形,就直接返回当前矩形内所有点的权值和就好了,否则就继续递归。

完整代码和注释如下:

#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;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值