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;
}
posted @ 2020-05-02 21:07  lnzwz  阅读(209)  评论(0编辑  收藏  举报