K-D Tree 学习笔记

注:\(K-D\ Tree\) 的应用中由于大量用到了 \(dfs\) 剪枝,所以通常不是正解。但是由于他相当好写,而且通常跑的不慢,所以也广为流传。感觉像是一种半骗分思路。下文简称其为 \(KDT\)


一、\(K-D\ Tree\)

我们都知道 \(2D,3D\) 表示二维、三维,所以 \(KDT\) 也很好理解,就是 \(K\) 维的情况下建的树。

他的基本建树思路就是交替枚举坐标,每次取当前坐标 \(k\) 的中位数,将点集分成两个部分,继续建树。为了方便建树,我们可以使用 \(nth\_element\) 这个 \(STL\)。当然,此时的数组中点就是该树的根,记录自己的位置和这个 \(K\) 维矩形。

在插入时有两种思路(下文询问表示矩阵求和之类的,不是平面最近点对一类):

  1. 仿照替罪羊树,在树不平衡到一定程度时暴力拍平重构,单次修改时间复杂度均摊是 \(O(\sqrt{n\log n})\),单次询问时间复杂度均摊是 \(O(\sqrt{n\log n}+n^{1-\frac 1k})\)

  2. 采用二进制分组的方式,建立 \(\log n\)\(KDT\),第 \(i\) 棵大小为 \(2^i\)(编号从 \(0\) 开始),每次加点时,从 \(0\) 号树开始合并,直到出现空位,再将新的树放进去。查询遍历所有树即可。单次修改时间复杂度均摊为 \(O(\log^2n)\),单次询问时间复杂度均摊是 \(O(n^{1-\frac 1k})\)

我采用的是第二种方法。

二、平面最近最远点对

经典问题了。

你不会以为 \(KDT\) 有什么精妙绝伦的解法吧?

实际上此时 \(KDT\) 只能 \(dfs\) 求解。但是结合 \(A^*\) 与一个信竞生多年的 \(dfs\) 剪枝经验,我们可以发现两个优化:

  1. 假如将他插入 \(KDT\) 中,他在哪个子树,哪个子树就更有可能取到最小值。

  2. 假如我们走这棵子树,就算走到最近/远处也不优于当前答案,那么我们就不需要走这棵子树。这个判断可以使用估价函数。

那么我们就可以相对快速的求解了。

//SDOI2010 捉迷藏
#include<bits/stdc++.h>
#define ls(x) nd[x].ls
#define rs(x) nd[x].rs
#define id(x,y) nd[x].id[y]
#define lp(x,y) nd[x].lp[y]
#define rd(x,y) nd[x].rd[y]
using namespace std;
const int N=1e5+5;
int n,ans=2e9;
namespace kd_tree{
	int rt[25],a[N],tot,kw;
	struct node{
		int ls,rs,id[2];
		int lp[2],rd[2];
	}nd[N];int tp,m;
	int cmp(int x,int y){
		return id(x,kw)<id(y,kw);
	}int dis(int x,int y){
		return abs(id(x,0)-id(y,0))+abs(id(x,1)-id(y,1));
	}int rec1(int x,int y){
		int re=max(id(y,0)-rd(x,0),0)+max(lp(x,0)-id(y,0),0);
		return re+max(id(y,1)-rd(x,1),0)+max(lp(x,1)-id(y,1),0);
	}int rec2(int x,int y){
		int re=max(abs(id(y,0)-rd(x,0)),abs(id(y,0)-lp(x,0)));
		return re+max(abs(id(y,1)-rd(x,1)),abs(id(y,1)-lp(x,1)));
	}void push_up(int x){
		lp(x,0)=min({id(x,0),lp(ls(x),0),lp(rs(x),0)});
		lp(x,1)=min({id(x,1),lp(ls(x),1),lp(rs(x),1)});
		rd(x,0)=max({id(x,0),rd(ls(x),0),rd(rs(x),0)});
		rd(x,1)=max({id(x,1),rd(ls(x),1),rd(rs(x),1)});
	}int build(int l,int r,int k){
		int mid=(l+r)/2;kw=k;
		nth_element(a+l,a+mid,a+r+1,cmp);
		if(l<mid) ls(a[mid])=build(l,mid-1,k^1);
		if(r>mid) rs(a[mid])=build(mid+1,r,k^1);
		return push_up(a[mid]),a[mid];
	}void clear(int &x){
		a[++tp]=x;
		if(ls(x)) clear(ls(x));
		if(rs(x)) clear(rs(x));x=0;
	}void add(int x){
		a[tp=1]=x;int t=0;
		while(rt[t]) clear(rt[t]),t++;
		rt[t]=build(1,tp,0);
	}void insert(int x,int y){
		id(++m,0)=x,id(m,1)=y,add(m);
	}void que1(int x,int y,int k,int &re){
		if(x!=y) re=min(re,dis(x,y));
		if(id(y,k)<id(x,k)){
			if(ls(x)&&rec1(ls(x),y)<re) que1(ls(x),y,k^1,re);
			if(rs(x)&&rec1(rs(x),y)<re) que1(rs(x),y,k^1,re);
		}else{
			if(rs(x)&&rec1(rs(x),y)<re) que1(rs(x),y,k^1,re);
			if(ls(x)&&rec1(ls(x),y)<re) que1(ls(x),y,k^1,re);
		}
	}void que2(int x,int y,int k,int &re){
		if(x!=y) re=max(re,dis(x,y));
		if(id(y,k)>id(x,k)){
			if(ls(x)&&rec2(ls(x),y)>re) que2(ls(x),y,k^1,re);
			if(rs(x)&&rec2(rs(x),y)>re) que2(rs(x),y,k^1,re);
		}else{
			if(rs(x)&&rec2(rs(x),y)>re) que2(rs(x),y,k^1,re);
			if(ls(x)&&rec2(ls(x),y)>re) que2(ls(x),y,k^1,re);
		}
	}int qu1(int ij){
		int re=2e9,t=0;
		while(t<25){if(rt[t]) que1(rt[t],ij,0,re);t++;}
		return re;
	}int qu2(int ij){
		int re=0,t=0;
		while(t<25){if(rt[t]) que2(rt[t],ij,0,re);t++;}
		return re;
	}
}using namespace kd_tree;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n,lp(0,0)=lp(0,1)=2e9;
	for(int i=1,x,y;i<=n;i++)
		cin>>x>>y,insert(x,y);
	for(int i=1;i<=n;i++)
		ans=min(ans,qu2(i)-qu1(i));
	return cout<<ans,0;
}
posted @ 2024-12-27 10:29  长安一片月_22  阅读(6)  评论(0编辑  收藏  举报