kd 樹總結


kd 樹是一種分割 k 維數據空間的數據結構。
它通常被用來解決 k 維空間中的距離最值 ( 第 k 小值 ) 問題。
當然,它也能解決其它問題。

建樹的方法:
假設我們的平面上的點的序列爲 [l,r] 。
我們先選定一個維度爲基準,不妨假設是 x 維度。
然後我們找出 [l,r] 這些點按照 x 座標排序,找到 x 座標是中位數的那個點,把它加到樹上。
之後遞歸建該點的左兒子 ->( 在 [l,mid-1] 這些點中,以 y 維度爲基準 ) 。
再遞歸建該點的右兒子 ->( 在 [mid+1,r] 這些點中,以 y 維度爲基準 ) 。
其實這個基準就是一層按 x 維度,這層的下一層按 y 維度,再下一層按 x 維度 .....
在建樹的同時我們要知道樹上某點及其子樹確定的矩形範圍:只需記錄一個點的子樹中,x,y座標的最小,最大值即可。
空間複雜度是線性的。
KD樹有兩種常見應用:

一、查詢距離最值

比如:現在給定二維空間下的 n 個點,我們多次詢問這些點到給定某點的歐幾里得/曼哈頓距離的最小值。

從根查詢,查詢到樹上的某個點後,我們判斷該點到樹上該點確定的矩形範圍的距離值,並且通過該值與目前答案比較,進行剪枝。
如果左兒子的距離優於右兒子,那麼按照左兒子,右兒子的順序進行求值。反之按照右兒子,左兒子的順序進行求值。

如果用堆維護,可以把最小值變成前K小值。
時間複雜度:\(O(\sqrt{n})\)

例題:

代碼:

#include <stdio.h>
#include <algorithm>
#include <queue>
using namespace std;
#define ll long long
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
#define pf(x) 1ll*(x)*(x)
#define re register
ll dis(re int x1,re int y1,re int x2,re int y2)
{
	re ll tx=x1-x2,ty=y1-y2;
	return tx*tx+ty*ty;
}
int cl[100010],cr[100010],px[100010],py[100010];
int lx[100010],rx[100010],ly[100010],ry[100010],root;
void pushup(int i)
{
	lx[i]=rx[i]=px[i];
	ly[i]=ry[i]=py[i];
	if(cl[i]!=0)
	{
		lx[i]=min(lx[i],lx[cl[i]]);rx[i]=max(rx[i],rx[cl[i]]);
		ly[i]=min(ly[i],ly[cl[i]]);ry[i]=max(ry[i],ry[cl[i]]);
	}
	if(cr[i]!=0)
	{
		lx[i]=min(lx[i],lx[cr[i]]);rx[i]=max(rx[i],rx[cr[i]]);
		ly[i]=min(ly[i],ly[cr[i]]);ry[i]=max(ry[i],ry[cr[i]]);
	}
}
int cmpx(int a,int b)
{
	return px[a]<px[b];
}
int cmpy(int a,int b)
{
	return py[a]<py[b];
}
int buiy(int sz[100010],int l,int r);
int buix(int sz[100010],int l,int r)
{
	if(l>r)return 0;
	int m=(l+r)>>1;
	nth_element(sz+l,sz+m,sz+r+1,cmpx);
	int rt=sz[m];
	cl[rt]=buiy(sz,l,m-1);
	cr[rt]=buiy(sz,m+1,r);
	pushup(rt);
	return rt;
}
int buiy(int sz[100010],int l,int r)
{
	if(l>r)return 0;
	int m=(l+r)>>1;
	nth_element(sz+l,sz+m,sz+r+1,cmpy);
	int rt=sz[m];
	cl[rt]=buix(sz,l,m-1);
	cr[rt]=buix(sz,m+1,r);
	pushup(rt);
	return rt;
}
inline ll getmax(re int x,re int y,re int i)
{
	return max(pf(x-lx[i]),pf(x-rx[i]))+max(pf(y-ly[i]),pf(y-ry[i]));
}
struct SJd
{
	int i;
	ll z;
	SJd(){}
	SJd(int I,ll Z)
	{
		i=I;z=Z;
	}
};
bool operator<(const SJd a,const SJd b)
{
	if(a.z==b.z)
		return a.i<b.i;
	return a.z>b.z;
}
priority_queue<SJd> pq;
void dfs(re int u,re int x,re int y)
{
	re SJd t=SJd(u,dis(x,y,px[u],py[u]));
	if(t<pq.top())
	{
		pq.pop();
		pq.push(t);
	}
	ll d1=getmax(x,y,cl[u]),d2=getmax(x,y,cr[u]);
	if(d1>d2)
	{
		if(cl[u]!=0&&d1>=pq.top().z)
			dfs(cl[u],x,y);
		if(cr[u]!=0&&d2>=pq.top().z)
			dfs(cr[u],x,y);
	}
	else
	{
		if(cr[u]!=0&&d2>=pq.top().z)
			dfs(cr[u],x,y);
		if(cl[u]!=0&&d1>=pq.top().z)
			dfs(cl[u],x,y);
	}
}
int getans(int x,int y,int k)
{
	while(!pq.empty())
		pq.pop();
	for(int i=0;i<k;i++)
		pq.push(SJd(-1,-1));
	dfs(root,x,y);
	return pq.top().i;
}
int sz[100010];
int main()
{
	int n,m;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&px[i],&py[i]);
	scanf("%d",&m);
	for(int i=1;i<=n;i++)
		sz[i]=i;
	root=buix(sz,1,n);
	for(int i=0;i<m;i++)
	{
		int x,y,k;
		scanf("%d%d%d",&x,&y,&k);
		printf("%d\n",getans(x,y,k));
	}
	return 0;
}

二、矩形修改,矩形查詢

比如:區間連邊的最短路,半平面覆蓋等。
從根出發,查詢到樹上的某個點後,若這個點的矩形區域完全包含在修改區域中,則直接打標記。
若這個點的矩形區域和修改區域沒有交集,則直接返回。
查詢和修改類似,注意儘量剪枝。
有點類似線段樹。
但要注意,遍歷到一個點時,要把這個點單獨考慮一下。
時間複雜度未知,但通常很快。

例題1:

例題2:
每次刪去一個半平面中的點,問每個點的第一次刪除的時間。
因爲每個點只考慮第一次,因此暴力刪除即可。
可以添加一個剪枝:記錄子樹中還有幾個點。
這樣,如果子樹空了就直接返回。

代碼:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define eps 1e-7
double max(double a,double b)
{
	return a>b?a:b;
}
double min(double a,double b)
{
	return a<b?a:b;
}
int sgn(double x)
{
	if(x>eps)
		return 1;
	else if(x<-eps)
		return -1;
	return 0;
}
struct SPx
{
	double x,y;
	int i;
};
int cmpx(const void*a,const void*b)
{
	return sgn(((SPx*)a)->x-((SPx*)b)->x);
}
int cmpy(const void*a,const void*b)
{
	return sgn(((SPx*)a)->y-((SPx*)b)->y);
}
double lx[100010],ly[100010],rx[100010],ry[100010],X[100010],Y[100010];
int cl[100010],cr[100010],si[100010],ans[100010];
int buiy(SPx sz[100010],int l,int r);
void up(int rt)
{
	si[rt]=si[cl[rt]]+si[cr[rt]]+(ans[rt]==-1);
}
void pushup(int rt)
{
    if(cl[rt]!=0)
    {
        lx[rt]=min(lx[rt],lx[cl[rt]]);rx[rt]=max(rx[rt],rx[cl[rt]]);
        ly[rt]=min(ly[rt],ly[cl[rt]]);ry[rt]=max(ry[rt],ry[cl[rt]]);
    }
    if(cr[rt]!=0)
    {
        lx[rt]=min(lx[rt],lx[cr[rt]]);rx[rt]=max(rx[rt],rx[cr[rt]]);
        ly[rt]=min(ly[rt],ly[cr[rt]]);ry[rt]=max(ry[rt],ry[cr[rt]]);
    }
	up(rt);
}
int buix(SPx sz[100010],int l,int r)
{
    if(l>=r)return 0;
    qsort(sz+l,r-l,sizeof(SPx),cmpx);
    int m=(l+r-1)>>1,rt=sz[m].i;
    lx[rt]=rx[rt]=sz[m].x;
    ly[rt]=ry[rt]=sz[m].y;
    cl[rt]=buiy(sz,l,m);
    cr[rt]=buiy(sz,m+1,r);
	pushup(rt);
    return rt;
}
int buiy(SPx sz[100010],int l,int r)
{
    if(l>=r)return 0;
    qsort(sz+l,r-l,sizeof(SPx),cmpy);
    int m=(l+r-1)>>1,rt=sz[m].i;
    lx[rt]=rx[rt]=sz[m].x;
    ly[rt]=ry[rt]=sz[m].y;
    cl[rt]=buix(sz,l,m);
    cr[rt]=buix(sz,m+1,r);
	pushup(rt);
    return rt;
}
SPx px[100010];
bool inside(double x,double y,double k,double a)
{
	return sgn(y-(k-x*a))>0;
}
bool inside(int i,double k,double a)
{
	return inside(lx[i],ly[i],k,a);
}
bool outside(int i,double k,double a)
{
	return !inside(rx[i],ry[i],k,a);
}
void mark(int u,int x)
{
	if(u==0)
		return;
	if(ans[u]==-1)
		ans[u]=x;
	si[u]=0;
	mark(cl[u],x);
	mark(cr[u],x);
}
void fugai(int u,double k,double a,int x)
{
	if(si[u]==0||outside(u,k,a))
		return;
	if(inside(u,k,a))
	{
		mark(u,x);
		return;
	}
	if(ans[u]==-1&&inside(X[u],Y[u],k,a))
		ans[u]=x;
	fugai(cl[u],k,a,x);fugai(cr[u],k,a,x);
	up(u);
}
int main()
{
	freopen("point.in","r",stdin);
	freopen("point.out","w",stdout);
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		double x,y;
		scanf("%lf%lf",&x,&y);
		X[i]=px[i].x=log(x);
		Y[i]=px[i].y=log(y);
		ans[i]=-1;px[i].i=i;
	}
	int ro=buix(px,1,n+1);
	for(int i=1;i<=m;i++)
	{
		double k,a;
		scanf("%lf%lf",&k,&a);
		k=log(k);
		fugai(ro,k,a,i);
	}
	for(int i=1;i<=n;i++)
		printf("%d\n",ans[i]);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章