Description
这天,SJY显得无聊。在家自己玩。在一个棋盘上,有N个黑色棋子。他每次要么放到棋盘上一个黑色棋子,要么放上一个白色棋子,如果是白色棋子,他会找出距离这个白色棋子最近的黑色棋子。此处的距离是 曼哈顿距离 即(|x1-x2|+|y1-y2|) 。现在给出N<=500000个初始棋子。和M<=500000个操作。对于每个白色棋子,输出距离这个白色棋子最近的黑色棋子的距离。同一个格子可能有多个棋子。
Input
第一行两个整数N和M;
接下来N行,每行为一个点的坐标;
接下来M行,每行3个数t,x,y;
如果t=1,那么放下一个黑色棋子;
如果t=2,那么放下一个白色棋子;
Output
对于每个T=2 输出一个最小距离
Sample Input
2 3 1 1 2 3 2 1 2 1 3 3 2 4 2
Sample Output
1 2
这天,我闲的无聊。来做kd-tree的题。
kd-tree一般用来解决二维问题,当然高维也可以,时间复杂度O(玄学)O(n^((k-1)/k))
主要可用来解决两类问题:
1、最近邻点的查找(或精确查找) ——此题要求的是这个
2、范围查找(统计平面(多维空间)内的权值和等)。
结构定义如下:
struct Node{
int p[k];//维数
int s[k];//儿子
int l[k],r[k];//范围
bool operator<(const Node&A)const{
return p[nowst]<A.p[nowst];//nowst为全局变量
}
};
是的,不看p[],像线段树;不看l[],r[],像平衡树。个人倾向于像线段树,不过kd-tree每个节点都有其实际意义。
1、考虑建树过程:
对于一维,我们考虑BST,针对多维的时候,我们按层BST,比如k=3(p数组为坐标)
第一层:按p[0]排序,分成左右区间
第二层:按p[1]排序,分成左右区间
第三层:按p[2]排序,分成左右区间
第四层:按p[0]排序……
直到叶子结点。
画图发现,我们通过坐标点将坐标系分成了一个个举行(二维下)。
2、考虑查询:
考虑暴力查找,有个显然的最优性剪枝是先找离它近的,我们的kd-tree也是这样= =
那么能够感受出kd-tree十分玄学,本质上竟然是个暴力剪枝,想卡是很容易的。比如查询最近点,我们是通过判左右儿子的距离来定优先遍历顺序的,试想所有点等距离分布在一个圆上,圆心还有一个点。。。那么你将会把所有点遍历一遍= =|||
(貌似没有什么破的方法,因此能不写尽量就不写kd-tree吧)
3、考虑插入:
暴力插入,类似于平衡树的插入,不过每次判断要根据当前层的划分依据判断。
是的,显然要考虑重构问题,我们设一个阈值,每次达到之后暴力重构用替罪羊重构,时间复杂度是可以接受的。(然而这道题重构了并没有什么加速)
if(kd.cnt==sz){
for(int i=1;i<=sz;++i)a[i]=kd.T[i];
kd.rebuild(kd.root,1,sz,0);sz+=10000;
}
4、考虑删除(此题不用)
kd-tree的删除题目十分少,而且删除一次是O(n)的,因此不多讲2333(我也不会)
#include<bits/stdc++.h>
using namespace std;
const int Maxn=1000005;
int n,m,nowst;
struct KD_Tree{
int cnt,root;
struct Node{
int p[2];//维数
int s[2];//儿子
int l[2],r[2];//范围
bool operator<(const Node&A)const{
return p[nowst]<A.p[nowst];
}
void newnode(int *_p,int k){
for(int i=0;i<k;++i){l[i]=r[i]=p[i]=_p[i];s[i]=0;}
}
}a[Maxn],nd;
void maintain(int x,int k){
for(int i=0;i<2;++i)if(a[x].s[i]){
for(int j=0;j<k;++j)//维护管辖范围
a[x].l[j]=min(a[x].l[j],a[a[x].s[i]].l[j]),
a[x].r[j]=max(a[x].r[j],a[a[x].s[i]].r[j]);
}
}
void Build(int &x,int k,int l,int r,int state){
if(l>r)return ;
x=l+r>>1;
nowst=state;
nth_element(a+l,a+x,a+r+1);
//系统函数,左闭中闭右开,将第x大放到中间,左边比他小(无序),右边比他小(无序),复杂度O(n)?不太了解,貌似要快一些
nd=a[x];a[x].newnode(nd.p,k);
Build(a[x].s[0],k,l,x-1,state^1);
Build(a[x].s[1],k,x+1,r,state^1);
maintain(x,k);
}
bool check(Node A,Node B,int k){
for(int i=0;i<k;++i)
if(A.p[i]!=B.p[i])return 0;
return 1;
}
inline void insert(int &x,int k,int state){
if(!x)return a[x=++cnt].newnode(nd.p,k),void();
if(check(nd,a[x],k))return ;//对于重复节点,自行维护
if(nd.p[state]<a[x].p[state])insert(a[x].s[0],k,state^1);
else insert(a[x].s[1],k,state^1);
maintain(x,k);
}
int dist(Node x,Node y){//如果包含则为0 ,不包含则是极限最近距离
return max(0,x.p[0]-y.r[0])+max(0,y.l[0]-x.p[0])+max(0,x.p[1]-y.r[1])+max(0,y.l[1]-x.p[1]);
}
void query(int x,int &ret){
ret=min(ret,abs(a[x].p[0]-nd.p[0])+abs(a[x].p[1]-nd.p[1]));
int dl=a[x].s[0]?dist(nd,a[a[x].s[0]]):0x3f3f3f3f;
int dr=a[x].s[1]?dist(nd,a[a[x].s[1]]):0x3f3f3f3f;
if(dl<dr){//是的,最优性剪枝
if(dl<ret)query(a[x].s[0],ret);
if(dr<ret)query(a[x].s[1],ret);
}else {
if(dr<ret)query(a[x].s[1],ret);
if(dl<ret)query(a[x].s[0],ret);
}
}
}kd;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
for(int j=0;j<2;++j)scanf("%d",&kd.nd.p[j]);
kd.a[++kd.cnt]=kd.nd;
}
kd.Build(kd.root,2,1,kd.cnt,0);
for(int i=1;i<=m;++i){
int c;scanf("%d",&c);
for(int j=0;j<2;++j)scanf("%d",&kd.nd.p[j]);
if(c==1)kd.insert(kd.root,2,0);
else {
int Ans=0x3f3f3f3f;
kd.query(kd.root,Ans);
cout<<Ans<<'\n';
}
}
return 0;
}