• KD-Tree 学习笔记


    定义

    (kd-tree)(k-dimensional)树的简称),是一种对(k)维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。

    主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索)。(K-D)树是二进制空间分割树的特殊的情况。

    在计算机科学里,(k-d)树( (k-)维树的缩写)是在(k)维欧几里德空间组织点的数据结构。

    (k-d) 树可以使用在多种应用场合,如多维键值搜索(例:范围搜寻及最邻近搜索)。k(-d)树是空间二分树((Binary space partitioning ))的一种特殊情况。

    设问题维度是 (K),其单次查询的复杂度大概是 (O(n^{frac{K−1}{K}}))

    大多数情况下,(kd-tree) 处理的都是二维空间上的问题

    树的建立

    采用递归的方式实现,类似于线段树

    我们设置一个变量 (pl) 表示当前以哪一维作为基准排序

    递归到某一层时,取当前这一维的中位数,把比中位数小的分到左子树,比中位数大的分到右子树

    这样查询的时候会更快,类似于切面包

    通常的写法是在几个维度之间交替选取 (pl),还有的写法是选择几个维度中方差最小的,甚至还可以 (rand) 一个维度

    代码

    struct trr{
    	int d[2],mx[2],mn[2],lc,rc;
    	friend bool operator < (trr aa,trr bb){
    		return aa.d[orz]<bb.d[orz];
    	}
    }tr[maxn],jl[maxn];
    void push_up(rg int da){
    	rg int lc=tr[da].lc,rc=tr[da].rc;
    	for(rg int i=0;i<2;i++){
    		tr[da].mx[i]=tr[da].mn[i]=tr[da].d[i];
    		if(lc){
    			tr[da].mx[i]=std::max(tr[da].mx[i],tr[lc].mx[i]);
    			tr[da].mn[i]=std::min(tr[da].mn[i],tr[lc].mn[i]);
    		}
    		if(rc){
    			tr[da].mx[i]=std::max(tr[da].mx[i],tr[rc].mx[i]);
    			tr[da].mn[i]=std::min(tr[da].mn[i],tr[rc].mn[i]);
    		}
    	}
    }
    int build(rg int l,rg int r,rg int pl){
    	orz=pl;
    	rg int mids=(l+r)>>1;
    	std::nth_element(jl+l,jl+mids,jl+r+1);
    	tr[mids]=jl[mids];
    	if(mids>l) tr[mids].lc=build(l,mids-1,!pl);
    	if(r>mids) tr[mids].rc=build(mids+1,r,!pl);
    	push_up(mids);
    	return mids;
    }
    

    查询

    类似于 (dfs) 的剪枝,如果当前的子树全在要查询的范围之内,直接返回整棵子树的答案

    如果当前的子树全在要查询的范围之外,直接剪掉这棵子树

    否则继续在左右子树之间递归

    下面放几个比较常用的操作

    查询最近点对

    写法一

    int mindis(rg int da,rg int xx,rg int yy){
    	return Max(0,xx-tr[da].mx[0])+Max(0,tr[da].mn[0]-xx)+Max(0,yy-tr[da].mx[1])+Max(0,tr[da].mn[1]-yy);
    }
    void cxmin(rg int da,rg int xx,rg int yy){
    	rg int haha=dis(xx,yy,tr[da].d[0],tr[da].d[1]);
    	if(haha!=0) nans=Min(nans,haha);
    	rg int lc=tr[da].lc,rc=tr[da].rc;
    	if(lc){
    		if(mindis(lc,xx,yy)<nans) cxmin(lc,xx,yy);
    	}
    	if(rc){
    		if(mindis(rc,xx,yy)<nans) cxmin(rc,xx,yy);
    	}
    }
    

    写法二

    int mindis(rg int da,rg int xx,rg int yy){
    	return Max(0,xx-tr[da].mx[0])+Max(0,tr[da].mn[0]-xx)+Max(0,yy-tr[da].mx[1])+Max(0,tr[da].mn[1]-yy);
    }
    void cxmin(rg int da,rg int xx,rg int yy){
    	if(!da) return;
    	rg int haha=dis(xx,yy,tr[da].d[0],tr[da].d[1]);
    	if(haha!=0) nans=Min(nans,haha);
    	rg int lc=tr[da].lc,rc=tr[da].rc;
    	rg int jl1=INF,jl2=INF;
    	if(lc) jl1=mindis(lc,xx,yy);
    	if(rc) jl2=mindis(rc,xx,yy);
    	if(jl1<jl2){
    		if(jl1<nans) cxmin(lc,xx,yy);
    		if(jl2<nans) cxmin(rc,xx,yy);
    	} else {
    		if(jl2<nans) cxmin(rc,xx,yy);
    		if(jl1<nans) cxmin(lc,xx,yy);
    	}
    }
    

    写法二的查询函数和写法一有所不同

    虽然是一个小小的优化,但是剪枝效果超群

    查询最远点对

    int maxdis(rg int da,rg int xx,rg int yy){
    	return Max(Max(dis(xx,yy,tr[da].mn[0],tr[da].mn[1]),dis(xx,yy,tr[da].mx[0],tr[da].mn[1])),Max(dis(xx,yy,tr[da].mn[0],tr[da].mx[1]),dis(xx,yy,tr[da].mx[0],tr[da].mx[1])));
    }
    void cxmax(rg int da,rg int xx,rg int yy){
    	if(!da) return;
    	nans=Max(nans,dis(xx,yy,tr[da].d[0],tr[da].d[1]));
    	rg int lc=tr[da].lc,rc=tr[da].rc;
    	rg int jl1=-INF,jl2=-INF;
    	if(lc) jl1=maxdis(lc,xx,yy);
    	if(rc) jl2=maxdis(rc,xx,yy);
    	if(jl1>jl2){
    		if(jl1>nans) cxmax(lc,xx,yy);
    		if(jl2>nans) cxmax(rc,xx,yy);
    	} else {
    		if(jl2>nans) cxmax(rc,xx,yy);
    		if(jl1>nans) cxmax(lc,xx,yy);
    	}
    }
    

    查询 (k) 远点对

    维护一个有(k)个元素的小根堆,每次和堆顶元素比较

    long long maxdis(rg int da,rg int xx,rg int yy){
    	return std::max(std::max(dis(xx,yy,tr[da].mn[0],tr[da].mn[1]),dis(xx,yy,tr[da].mx[0],tr[da].mn[1])),std::max(dis(xx,yy,tr[da].mn[0],tr[da].mx[1]),dis(xx,yy,tr[da].mx[0],tr[da].mx[1])));
    }
    void cxmax(rg int da,rg int xx,rg int yy){
    	if(!da) return;
    	rg long long now=dis(xx,yy,tr[da].d[0],tr[da].d[1]);
    	if(now>q.top()){
    		q.pop();
    		q.push(now);
    	}
    	rg int lc=tr[da].lc,rc=tr[da].rc;
    	rg long long jl1=-0x3f3f3f3f3f3f3f3f,jl2=-0x3f3f3f3f3f3f3f3f;
    	if(lc) jl1=maxdis(lc,xx,yy);
    	if(rc) jl2=maxdis(rc,xx,yy);
    	if(jl1>jl2){
    		if(jl1>q.top()) cxmax(lc,xx,yy);
    		if(jl2>q.top()) cxmax(rc,xx,yy);
    	} else {
    		if(jl2>q.top()) cxmax(rc,xx,yy);
    		if(jl1>q.top()) cxmax(lc,xx,yy);
    	}
    }
    

    维护信息

    (kd-tree) 也可以维护各种各样的信息

    和线段树基本类似,但是(kd-tree)的每一个节点都是真是存在的点

    修改的时候也要注意 (push\_up)(push\_down)

    加点操作

    在左右子树之间递归即可

    int ad(rg int da,rg int aa,rg int bb,rg int pl){
    	if(!da){
    		da=++n;
    		tr[da].d[0]=aa;
    		tr[da].d[1]=bb;
    		push_up(da);
    		return da;
    	}
    	if(pl==0){
    		if(tr[da].d[pl]<aa) tr[da].lc=ad(tr[da].lc,aa,bb,!pl);
    		else tr[da].rc=ad(tr[da].rc,aa,bb,!pl);
    	} else {
    		if(tr[da].d[pl]<bb) tr[da].lc=ad(tr[da].lc,aa,bb,!pl);
    		else tr[da].rc=ad(tr[da].rc,aa,bb,!pl);
    	}
    	push_up(da);
    	return da;
    }
    

    但是某些毒瘤出题人会精心构造数据把你的树卡成一条链

    所以我们需要根据替罪羊的思想排扁重构

    当某个子树的大小乘上一个平衡因子大于整棵树的大小就要重构

    平衡因子一般设为 (0.75)

    重构时,我们只需要把所有需要重构的点拿出来再建一遍树即可

    代码(SYZ摆棋子)

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<iostream>
    #include<vector>
    #define rg register
    inline int read(){
    	rg int x=0,fh=1;
    	rg char ch=getchar();
    	while(ch<'0' || ch>'9'){
    		if(ch=='-') fh=-1;
    		ch=getchar();
    	}
    	while(ch>='0' && ch<='9'){
    		x=(x<<1)+(x<<3)+(ch^48);
    		ch=getchar();
    	}
    	return x*fh;
    }
    const int maxn=1e6+5;
    const double alpha=0.75;
    int n,m,ans,orz,rt,sta[maxn],tp,cnt;
    std::vector<int> xx,yy;
    struct trr{
    	int d[2],mx[2],mn[2],lc,rc,siz;
    	friend bool operator < (trr aa,trr bb){
    		return aa.d[orz]<bb.d[orz];
    	}
    }tr[maxn],jl[maxn];
    void push_up(rg int da){
    	rg int lc=tr[da].lc,rc=tr[da].rc;
    	for(rg int i=0;i<2;i++){
    		tr[da].mx[i]=tr[da].mn[i]=tr[da].d[i];
    		if(lc){
    			tr[da].mx[i]=std::max(tr[da].mx[i],tr[lc].mx[i]);
    			tr[da].mn[i]=std::min(tr[da].mn[i],tr[lc].mn[i]);
    		}
    		if(rc){
    			tr[da].mx[i]=std::max(tr[da].mx[i],tr[rc].mx[i]);
    			tr[da].mn[i]=std::min(tr[da].mn[i],tr[rc].mn[i]);
    		}
    	}
    	tr[da].siz=tr[lc].siz+tr[rc].siz+1;
    }
    int newnode(){
    	if(tp) return sta[tp--];
    	else return ++cnt; 
    }
    int build(rg int l,rg int r,rg int pl){
    	orz=pl;
    	rg int mids=(l+r)>>1,da=newnode();
    	std::nth_element(jl+l,jl+mids,jl+r+1);
    	tr[da]=jl[mids];
    	if(mids>l) tr[da].lc=build(l,mids-1,!pl);
    	if(r>mids) tr[da].rc=build(mids+1,r,!pl);
    	push_up(da);
    	return da;
    }
    int Max(rg int aa,rg int bb){
    	return aa>bb?aa:bb;
    }
    int Min(rg int aa,rg int bb){
    	return aa<bb?aa:bb;
    }
    int Abs(rg int aa){
    	return aa<0?-aa:aa;
    }
    int mindis(rg int da,rg int aa,rg int bb){
    	rg int nans=0;
    	nans+=Max(0,aa-tr[da].mx[0]);
    	nans+=Max(0,tr[da].mn[0]-aa);
    	nans+=Max(0,bb-tr[da].mx[1]);
    	nans+=Max(0,tr[da].mn[1]-bb);
    	return nans;
    }
    int getdis(rg int da,rg int aa,rg int bb){
    	return Abs(aa-tr[da].d[0])+Abs(bb-tr[da].d[1]);
    }
    void cx(rg int da,rg int aa,rg int bb){
    	rg int lc=tr[da].lc,rc=tr[da].rc;
    	ans=Min(ans,getdis(da,aa,bb));
    	rg int ac1=0x3f3f3f3f,ac2=0x3f3f3f3f;
    	if(lc) ac1=mindis(lc,aa,bb);
    	if(rc) ac2=mindis(rc,aa,bb);
    	if(ac1<ac2){
    		if(ac1<ans) cx(lc,aa,bb);
    		if(ac2<ans) cx(rc,aa,bb);
    	} else {
    		if(ac2<ans) cx(rc,aa,bb);
    		if(ac1<ans) cx(lc,aa,bb);
    	}
    }
    void init(rg int now){
    	sta[++tp]=now;
    	jl[tp].d[0]=tr[now].d[0];
    	jl[tp].d[1]=tr[now].d[1];
    	if(tr[now].lc) init(tr[now].lc);
    	if(tr[now].rc) init(tr[now].rc);
    }
    void check(rg int &da,rg int pl){
    	if(alpha*tr[da].siz<tr[tr[da].lc].siz || alpha*tr[da].siz<tr[tr[da].rc].siz){
    		tp=0;
    		init(da);
    		da=build(1,tr[da].siz,pl);
    	}
    }
    int ad(rg int da,rg int aa,rg int bb,rg int pl){
    	if(!da){
    		da=newnode();
    		tr[da].d[0]=aa;
    		tr[da].d[1]=bb;
    		push_up(da);
    		return da;
    	}
    	if(pl==0){
    		if(tr[da].d[pl]<aa) tr[da].lc=ad(tr[da].lc,aa,bb,!pl);
    		else tr[da].rc=ad(tr[da].rc,aa,bb,!pl);
    	} else {
    		if(tr[da].d[pl]<bb) tr[da].lc=ad(tr[da].lc,aa,bb,!pl);
    		else tr[da].rc=ad(tr[da].rc,aa,bb,!pl);
    	}
    	push_up(da);
    	check(da,pl);
    	return da;
    }
    int main(){
    	n=read(),m=read();
    	for(rg int i=1;i<=n;i++){
    		jl[i].d[0]=read(),jl[i].d[1]=read();
    	}
    	rt=build(1,n,0);
    	rg int aa,bb,cc;
    	for(rg int i=1;i<=m;i++){
    		aa=read(),bb=read(),cc=read();
    		if(aa==1){
    			ad(rt,bb,cc,0);
    		} else {
    			ans=0x3f3f3f3f;
    			cx(rt,bb,cc);
    			printf("%d
    ",ans);
    		}
    	}
    	return 0;
    }
    

    习题

    一般来说看到区间最远、最近、(k)远点对、平面上矩阵的问题都可以往 (kd-tree) 的方面想一想

    当然 (kd-tree) 还是有一些比较妙的操作的

    比如 [NOI2019]弹跳利用了(kd-tree) 优化建图

    Generating Synergy则把树上的操作转变为深度和 (dfn) 序两维限制,从而可以使用 (kd-tree)

    当不会正解的时候,(kd-tree)也可以作为骗分的利器

  • 相关阅读:
    android调用系统拍照那些事
    HttpClient4.5
    关于Android自带的定位处理
    ORMLite配置异常处理
    mysql绿色版配置
    android 震动和提示音
    Weblogic新增域(可以配置新端口)
    weblogic启动错误
    将程序部署到weblogic出现乱码问题
    SVN更新报错
  • 原文地址:https://www.cnblogs.com/liuchanglc/p/14177897.html
Copyright © 2020-2023  润新知