bzoj2648 SJY摆石子 kd-tree

       学习了一下kd-tree,感觉在某些平面问题上具有独特的优势呢。。

       kd-tree,就是用一直的n个点,将平面划分为n+1块,划分的方式:对于当前层,假设以x坐标为关键字,找到x坐标在中间的点p,然后将这个区间分成两块,一块的x坐标都比p小,另一块的x坐标都比p大;然后分别递归两块,但是那两块的关键字就是y坐标了。然后就一层x坐标一层y坐标递归下去,还是很直观的。(还有根据当前层的情况来决定以x坐标还是y坐标为关键字的,这里略去)

       插入一个点,就找到这个点所在的块,然后根据父亲节点的关键字是横坐标还是纵坐标把所在的块分成两块。虽然这样最坏是O(N)级别的。(可能可用替罪羊树的暴力重构思想?不过我连替罪羊树都不会写这个怎么可能会写)

       查询一个点p。一样还是找到它所在的块,然后首先ans=dist(p,该块对应的点)。然后每次回溯时,由于一个点把它所在的块分成了两份,而其中一份包含了p,我们先看一下以p为圆心,ans为半径画圆(欧几里得距离是圆,曼哈顿距离是一个正方形),看是否会和另一份相交,如果相交就查询另一份。由于有时会两个子节点都查询,所以不是O(logN)的,可能是O(N^0.5)?

       然后这道题目就是裸题辣2333~\(≧▽≦)/~

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1000005
#define inf 1000000000
using namespace std;

int n,m,dim,rt,ans;
int read(){
	int x=0; char ch=getchar();
	while (ch<'0' || ch>'9') ch=getchar();
	while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
	return x;
}
struct node{ int p[2],x[2],y[2]; }a[N];
bool cmp(node x,node y){ return x.p[dim]<y.p[dim]; }
struct kd_tree{
	int c[N][2]; node s[N],q;
	void maintain(int k){
		int l=c[k][0],r=c[k][1],i;
		for (i=0; i<2; i++){
			if (l){ s[k].x[i]=min(s[k].x[i],s[l].x[i]); s[k].y[i]=max(s[k].y[i],s[l].y[i]); }
			if (r){ s[k].x[i]=min(s[k].x[i],s[r].x[i]); s[k].y[i]=max(s[k].y[i],s[r].y[i]); }
		}
	}
	void add(int k,node t){
		int i; for (i=0; i<2; i++) s[k].x[i]=s[k].y[i]=s[k].p[i]=t.p[i];
	}
	int dist(node t,int k){
		int tmp=0,i;
		for (i=0; i<2; i++) tmp+=max(0,s[k].x[i]-t.p[i]);
		for (i=0; i<2; i++) tmp+=max(0,t.p[i]-s[k].y[i]);
		return tmp;
	}
	void build(int &k,int l,int r,int now){
		k=(l+r)>>1; dim=now;
		nth_element(a+l,a+k,a+r+1,cmp);
		add(k,a[k]);
		if (l<k) build(c[k][0],l,k-1,now^1);
		if (k<r) build(c[k][1],k+1,r,now^1);
		maintain(k);
	}
	void ins(int k,int now){
		if (q.p[now]<s[k].p[now])
			if (c[k][0]) ins(c[k][0],now^1); else{
				c[k][0]=++n; add(n,q);
			}
		else
			if (c[k][1]) ins(c[k][1],now^1); else{
				c[k][1]=++n; add(n,q);
			}
		maintain(k);
	}
	void qry(int k){
		ans=min(ans,abs(s[k].p[0]-q.p[0])+abs(s[k].p[1]-q.p[1]));
		int dl=(c[k][0])?dist(q,c[k][0]):inf,dr=(c[k][1])?dist(q,c[k][1]):inf;
		if (dl<dr){
			if (dl<ans) qry(c[k][0]); if (dr<ans) qry(c[k][1]);
		} else{
			if (dr<ans) qry(c[k][1]); if (dl<ans) qry(c[k][0]);
		}
	}
}kd;
int main(){
	n=read(); m=read(); int i;
	for (i=1; i<=n; i++) scanf("%d%d",&a[i].p[0],&a[i].p[1]);
	kd.build(rt,1,n,0);
	while (m--){
		int k=read(); kd.q.p[0]=read(); kd.q.p[1]=read();
		if (k==1) kd.ins(rt,0); else{
			ans=inf; kd.qry(rt); printf("%d\n",ans);
		}
	}
	return 0;
}


by lych

2016.3.5

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值