zoukankan      html  css  js  c++  java
  • 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)也可以作为骗分的利器

  • 相关阅读:
    剑指Offer-11.二进制中1的个数(C++/Java)
    剑指Offer-10.矩形覆盖(C++/Java)
    剑指Offer-9.变态跳台阶(C++/Java)
    UVA 1608 Non-boring sequence 不无聊的序列(分治,中途相遇)
    UVA1607 Gates 与非门电路 (二分)
    UVA 1451 Average平均值 (数形结合,斜率优化)
    UVA 1471 Defense Lines 防线 (LIS变形)
    UVA 1606 Amphiphilic Carbon Molecules 两亲性分子 (极角排序或叉积,扫描法)
    UVA 11134 FabledRooks 传说中的车 (问题分解)
    UVA 1152 4 Values Whose Sum is Zero 和为0的4个值 (中途相遇)
  • 原文地址:https://www.cnblogs.com/liuchanglc/p/14177897.html
Copyright © 2011-2022 走看看