BZOJ 2716/2648 SJY摆棋子

本文深入探讨了kd-tree数据结构的构建、查询、插入及删除原理,并通过一道具体题目展示了kd-tree在解决最近邻点查找问题上的应用。文章详细解释了kd-tree如何通过递归地按维度分割空间来构建树形结构,以及如何利用这种结构进行高效的最近邻点搜索。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

  这天,SJY显得无聊。在家自己玩。在一个棋盘上,有N个黑色棋子。他每次要么放到棋盘上一个黑色棋子,要么放上一个白色棋子,如果是白色棋子,他会找出距离这个白色棋子最近的黑色棋子。此处的距离是 曼哈顿距离 即(|x1-x2|+|y1-y2|) 。现在给出N<=500000个初始棋子。和M<=500000个操作。对于每个白色棋子,输出距离这个白色棋子最近的黑色棋子的距离。同一个格子可能有多个棋子。

Input

  第一行两个整数N和M;
  接下来N行,每行为一个点的坐标;
  接下来M行,每行3个数t,x,y;
  如果t=1,那么放下一个黑色棋子;
  如果t=2,那么放下一个白色棋子;

Output

  对于每个T=2 输出一个最小距离

Sample Input

2 3 1 1 2 3 2 1 2 1 3 3 2 4 2

Sample Output

1 2

这天,我闲的无聊。来做kd-tree的题。

kd-tree一般用来解决二维问题,当然高维也可以,时间复杂度O(玄学)O(n^((k-1)/k))

主要可用来解决两类问题:

1、最近邻点的查找(或精确查找) ——此题要求的是这个

2、范围查找(统计平面(多维空间)内的权值和等)。

结构定义如下:

struct Node{
	int p[k];//维数 
	int s[k];//儿子 
	int l[k],r[k];//范围 
	bool operator<(const Node&A)const{
		return p[nowst]<A.p[nowst];//nowst为全局变量 
	}
};

是的,不看p[],像线段树;不看l[],r[],像平衡树。个人倾向于像线段树,不过kd-tree每个节点都有其实际意义。

1、考虑建树过程:

对于一维,我们考虑BST,针对多维的时候,我们按层BST,比如k=3(p数组为坐标)

第一层:按p[0]排序,分成左右区间

第二层:按p[1]排序,分成左右区间

第三层:按p[2]排序,分成左右区间

第四层:按p[0]排序……

直到叶子结点。

画图发现,我们通过坐标点将坐标系分成了一个个举行(二维下)。

2、考虑查询:

考虑暴力查找,有个显然的最优性剪枝是先找离它近的,我们的kd-tree也是这样= =

那么能够感受出kd-tree十分玄学,本质上竟然是个暴力剪枝,想卡是很容易的。比如查询最近点,我们是通过判左右儿子的距离来定优先遍历顺序的,试想所有点等距离分布在一个圆上,圆心还有一个点。。。那么你将会把所有点遍历一遍= =|||

(貌似没有什么破的方法,因此能不写尽量就不写kd-tree吧)

3、考虑插入:

暴力插入,类似于平衡树的插入,不过每次判断要根据当前层的划分依据判断。

是的,显然要考虑重构问题,我们设一个阈值,每次达到之后暴力重构用替罪羊重构,时间复杂度是可以接受的。(然而这道题重构了并没有什么加速)

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;
}

4、考虑删除(此题不用)

kd-tree的删除题目十分少,而且删除一次是O(n)的,因此不多讲2333(我也不会)

 

#include<bits/stdc++.h>
using namespace std;
const int Maxn=1000005;
int n,m,nowst;
struct KD_Tree{
	int cnt,root;
	struct Node{
		int p[2];//维数 
		int s[2];//儿子 
		int l[2],r[2];//范围 
		bool operator<(const Node&A)const{
			return p[nowst]<A.p[nowst];
		}
		void newnode(int *_p,int k){
			for(int i=0;i<k;++i){l[i]=r[i]=p[i]=_p[i];s[i]=0;}
		}
	}a[Maxn],nd;
	void maintain(int x,int k){
		for(int i=0;i<2;++i)if(a[x].s[i]){
			for(int j=0;j<k;++j)//维护管辖范围 
				a[x].l[j]=min(a[x].l[j],a[a[x].s[i]].l[j]),
				a[x].r[j]=max(a[x].r[j],a[a[x].s[i]].r[j]);
		}
	}
	void Build(int &x,int k,int l,int r,int state){
		if(l>r)return ;
		x=l+r>>1;
		nowst=state;
		nth_element(a+l,a+x,a+r+1);
		//系统函数,左闭中闭右开,将第x大放到中间,左边比他小(无序),右边比他小(无序),复杂度O(n)?不太了解,貌似要快一些 
		nd=a[x];a[x].newnode(nd.p,k);
		Build(a[x].s[0],k,l,x-1,state^1);
		Build(a[x].s[1],k,x+1,r,state^1);
		maintain(x,k);
	}
	bool check(Node A,Node B,int k){
		for(int i=0;i<k;++i)
			if(A.p[i]!=B.p[i])return 0;
		return 1;
	}
	inline void insert(int &x,int k,int state){
		if(!x)return a[x=++cnt].newnode(nd.p,k),void();
		if(check(nd,a[x],k))return ;//对于重复节点,自行维护 
		if(nd.p[state]<a[x].p[state])insert(a[x].s[0],k,state^1);
		else insert(a[x].s[1],k,state^1);
		maintain(x,k);
	}
	int dist(Node x,Node y){//如果包含则为0 ,不包含则是极限最近距离 
		return max(0,x.p[0]-y.r[0])+max(0,y.l[0]-x.p[0])+max(0,x.p[1]-y.r[1])+max(0,y.l[1]-x.p[1]);
	}
	void query(int x,int &ret){
		ret=min(ret,abs(a[x].p[0]-nd.p[0])+abs(a[x].p[1]-nd.p[1]));
		int dl=a[x].s[0]?dist(nd,a[a[x].s[0]]):0x3f3f3f3f;
		int dr=a[x].s[1]?dist(nd,a[a[x].s[1]]):0x3f3f3f3f;
		if(dl<dr){//是的,最优性剪枝 
			if(dl<ret)query(a[x].s[0],ret);
			if(dr<ret)query(a[x].s[1],ret);
		}else {
			if(dr<ret)query(a[x].s[1],ret);
			if(dl<ret)query(a[x].s[0],ret);
		}
	}
}kd;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i){
		for(int j=0;j<2;++j)scanf("%d",&kd.nd.p[j]);
		kd.a[++kd.cnt]=kd.nd;
	}
	kd.Build(kd.root,2,1,kd.cnt,0);
	for(int i=1;i<=m;++i){
		int c;scanf("%d",&c);
		for(int j=0;j<2;++j)scanf("%d",&kd.nd.p[j]);
		if(c==1)kd.insert(kd.root,2,0);
		else {
			int Ans=0x3f3f3f3f;
			kd.query(kd.root,Ans);
			cout<<Ans<<'\n';
		}
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值