K-D Tree 学习笔记
注:\(K-D\ Tree\) 的应用中由于大量用到了 \(dfs\) 剪枝,所以通常不是正解。但是由于他相当好写,而且通常跑的不慢,所以也广为流传。感觉像是一种半骗分思路。下文简称其为 \(KDT\)。
一、\(K-D\ Tree\)
我们都知道 \(2D,3D\) 表示二维、三维,所以 \(KDT\) 也很好理解,就是 \(K\) 维的情况下建的树。
他的基本建树思路就是交替枚举坐标,每次取当前坐标 \(k\) 的中位数,将点集分成两个部分,继续建树。为了方便建树,我们可以使用 \(nth\_element\) 这个 \(STL\)。当然,此时的数组中点就是该树的根,记录自己的位置和这个 \(K\) 维矩形。
在插入时有两种思路(下文询问表示矩阵求和之类的,不是平面最近点对一类):
-
仿照替罪羊树,在树不平衡到一定程度时暴力拍平重构,单次修改时间复杂度均摊是 \(O(\sqrt{n\log n})\),单次询问时间复杂度均摊是 \(O(\sqrt{n\log n}+n^{1-\frac 1k})\)。
-
采用二进制分组的方式,建立 \(\log n\) 棵 \(KDT\),第 \(i\) 棵大小为 \(2^i\)(编号从 \(0\) 开始),每次加点时,从 \(0\) 号树开始合并,直到出现空位,再将新的树放进去。查询遍历所有树即可。单次修改时间复杂度均摊为 \(O(\log^2n)\),单次询问时间复杂度均摊是 \(O(n^{1-\frac 1k})\)。
我采用的是第二种方法。
二、平面最近最远点对
经典问题了。
你不会以为 \(KDT\) 有什么精妙绝伦的解法吧?
实际上此时 \(KDT\) 只能 \(dfs\) 求解。但是结合 \(A^*\) 与一个信竞生多年的 \(dfs\) 剪枝经验,我们可以发现两个优化:
-
假如将他插入 \(KDT\) 中,他在哪个子树,哪个子树就更有可能取到最小值。
-
假如我们走这棵子树,就算走到最近/远处也不优于当前答案,那么我们就不需要走这棵子树。这个判断可以使用估价函数。
那么我们就可以相对快速的求解了。
//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;
}