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
",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
",ans[i]);
return 0;
}