题目:
题解:
这道题目包括KD-tree的
1、build
构建的主体思想就是不断地寻找区间中点,
把区间划分成尽量均等的两份,不断地递归构建下去
我们先利用nth_element函数找到该区间中间的节点,把ta放到树的中间当做这棵树的根
nth_element函数
求一个容器中第n(大/小)的元素
函数参数:nth_element(first,nth,last,compare);
注意:nth_element函数会将第n(大/小)的元素放到第n的位置,
且比第n(大/小)的元素会放到(右边/左边)
之后递归构造左右区间,不要忘了把切割方向转变一下
这个D&cmpd就是当前的分割方向
在build函数内部,我们并没有维护每个节点代表的区间
这些任务都是在update里完成的
2、updata&cmp
在这个函数中我们进行了KDtree节点代表区间的维护
最大值最小值都是自己&左儿子&右儿子的并
3、insert
一个元素的插入会引起ta到根的路径上所有节点的变化
首先引起的就是管辖区间的变化
如果插入的节点在当前整棵kdtree管辖的区间之外,那就要重新维护mn和mx了
之后就是比较插入点和当前节点在D(规定切割方向上的大小了)
然后进行插入
在该坐标相等的情况下归入右节点
4、ask
函数中有三个变量 d0,dl,dr
d0表示当前点到查询点的距离
dl表示当前点左儿子到查询点的距离
dr表示当前点右儿子到查询点的距离
注意这里dl,dr中的左儿子右儿子都是区间(除了最后的叶子结点之外)
查询到点的距离都是到此区间的距离
如果dl|dr < ans
那最终的答案就有可能在ta们分管的区间内, 我们就要进行相应方向的查找
如果dl和dr都小于ans,那我们优先min(dl,dr)进行查找
这里相应方向的查找实际上就是第一位dalao讲的邻近搜索,不能因为看上去小的优 就 不搜索较大的那个,只要小于ans,就要去看看,说不定会更小呢?
代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
#define INF 1e9
using namespace std;
const int N=1000005;
struct hh{int d[2],l,r,mx[2],mn[2];}t[N];
int cmpd,opt,x,y,root,ans;
int cmp(const hh &a,const hh &b)
{
return ( (a.d[cmpd]<b.d[cmpd]) || (a.d[cmpd]==b.d[cmpd] && a.d[!cmpd]<b.d[!cmpd]) );
}
void updata(int id)
{
int lc=t[id].l,rc=t[id].r;
if (lc)
{
t[id].mn[0]=min(t[id].mn[0],t[lc].mn[0]);
t[id].mn[1]=min(t[id].mn[1],t[lc].mn[1]);
t[id].mx[0]=max(t[id].mx[0],t[lc].mx[0]);
t[id].mx[1]=max(t[id].mx[1],t[lc].mx[1]);
}
if (rc)
{
t[id].mn[0]=min(t[id].mn[0],t[rc].mn[0]);
t[id].mn[1]=min(t[id].mn[1],t[rc].mn[1]);
t[id].mx[0]=max(t[id].mx[0],t[rc].mx[0]);
t[id].mx[1]=max(t[id].mx[1],t[rc].mx[1]);
}
}
int build(int l,int r,int id)
{
cmpd=id;
int mid=(l+r)>>1;
nth_element(t+l+1,t+mid+1,t+r+1,cmp);
t[mid].mn[0]=t[mid].mx[0]=t[mid].d[0];
t[mid].mn[1]=t[mid].mx[1]=t[mid].d[1];
if (l!=mid) t[mid].l=build(l,mid-1,!id);
if (r!=mid) t[mid].r=build(mid+1,r,!id);
updata(mid); return mid;
}
void insert(int now)
{
int D,p;
D=0; p=root;
while (1)
{
if (t[now].mn[0]<t[p].mn[0]) t[p].mn[0]=t[now].mn[0];
if (t[now].mx[0]>t[p].mx[0]) t[p].mx[0]=t[now].mx[0];
if (t[now].mn[1]<t[p].mn[1]) t[p].mn[1]=t[now].mn[1];
if (t[now].mx[1]>t[p].mx[1]) t[p].mx[1]=t[now].mx[1];
if (t[now].d[D]>=t[p].d[D])
{
if (!t[p].r) {t[p].r=now; return;}
else p=t[p].r;
}
else
{
if (!t[p].l) {t[p].l=now; return;}
else p=t[p].l;
}
D=!D;
}
}
int dis(int now)
{
int d=0;
if (x<t[now].mn[0]) d+=t[now].mn[0]-x;//不管是哪个点都要加上的距离
if (x>t[now].mx[0]) d+=x-t[now].mx[0];
if (y<t[now].mn[1]) d+=t[now].mn[1]-y;
if (y>t[now].mx[1]) d+=y-t[now].mx[1];
return d;
}
void ask(int now)
{
int d0,dl,dr;
d0=abs(t[now].d[0]-x)+abs(t[now].d[1]-y);
if (d0<ans) ans=d0;
if (t[now].l) dl=dis(t[now].l); else dl=INF;//当前点的左儿子(区间)到目标点的距离
if (t[now].r) dr=dis(t[now].r); else dr=INF;//右儿子
if (dl<dr)
{
if (dl<ans) ask(t[now].l);
if (dr<ans) ask(t[now].r);
}
else
{
if (dr<ans) ask(t[now].r);
if (dl<ans) ask(t[now].l);
}
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%d%d",&t[i].d[0],&t[i].d[1]);
root=build(1,n,0);
for (int i=1;i<=m;i++)
{
scanf("%d%d%d",&opt,&x,&y);
if (opt==1)
{
n++;
t[n].mn[0]=t[n].mx[0]=t[n].d[0]=x;
t[n].mn[1]=t[n].mx[1]=t[n].d[1]=y;
insert(n);
}
else
{
ans=INF;
ask(root);
printf("%d\n",ans);
}
}
}
普及向:
又是新姿势啦,这次找到了某个dalao博主的KD-tree,题解引自这位up
每个kdtree节点维护的信息:
(d[0],d[1]) 该节点代表的子树的根节点
(l,r) 该节点的左右儿子
(mx[0],mx[1]) 该节点代表的子树管理的区间右上角
(mn[0],mn[1]) 该节点代表的子树管理的区间左下角)