【BZOJ2648】SJY摆棋子(KD-Tree)
大致题意: 在一个二维平面上现有\(N\)个棋子,有两种操作:增加一个棋子;查询离某个坐标最近的棋子离它的曼哈顿距离。
\(KD-Tree\)
这是一道\(KD-Tree\)的乱搞题。
如何询问
在此题中,建树、插入、重构等过程其实与普通的\(KD-Tree\)是一样的,关键就在于如何询问。
对于当前处理到的节点,第一步当然是更新\(ans\):\(ans=min(ans,node[x].get\_dis(v))\)。
由于\(KD-Tree\)的性质,所以当前节点的两棵子树中的节点肯定位于两个互不相交的矩形中。
于是我们可以考虑求出当前询问的点到这两个矩形的距离(若在矩形内则距离为\(0\))。
这样一来就有了第一个剪枝:只有当这个距离\(<ans\)时,我们才需要访问该子树(证明:当前询问的点到这个矩形中任意一点肯定\(\ge\)到这个矩形的距离)。
然后,我们就可以分别求解了。
注意,求解的顺序也是有学问的,这就是第二个剪枝:按照当前询问点到矩形的距离从小到大进行求解,这样有利于求出更小的\(ans\),从而进一步剪枝。
这样一来,就将复杂度成功卡到了\(O(玄学)\)。
代码
#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define uint unsigned int
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define abs(x) ((x)<0?-(x):(x))
#define INF 1e9
#define Inc(x,y) ((x+=(y))>=MOD&&(x-=MOD))
#define ten(x) (((x)<<3)+((x)<<1))
#define N 300000
using namespace std;
int d;
struct point
{
int s[2];
inline friend bool operator < (point x,point y) {return x.s[d]<y.s[d];}
point(int x=0,int y=0) {s[0]=x,s[1]=y;}
}s[N+5];
class FIO
{
private:
#define Fsize 100000
#define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
#define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
int f,FoutSize,OutputTop;char ch,Fin[Fsize],*FinNow,*FinEnd,Fout[Fsize],OutputStack[Fsize];
public:
FIO() {FinNow=FinEnd=Fin;}
inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=ten(x)+(ch&15),isdigit(ch=tc()));x*=f;}
inline void read_char(char &x) {while(isspace(x=tc()));}
inline void read_string(string &x) {x="";while(isspace(ch=tc()));while(x+=ch,!isspace(ch=tc())) if(!~ch) return;}
inline void write(int x) {if(!x) return (void)pc('0');if(x<0) pc('-'),x=-x;while(x) OutputStack[++OutputTop]=x%10+48,x/=10;while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;}
inline void write_char(char x) {pc(x);}
inline void write_string(string x) {register int i,len=x.length();for(i=0;i<len;++i) pc(x[i]);}
inline void end() {fwrite(Fout,1,FoutSize,stdout);}
}F;
class Class_TwoDTree//2D-Tree
{
private:
#define alpha 0.75
#define balance(x) (alpha*node[x].Size>=1.0*max(node[node[x].Son[0]].Size,node[node[x].Son[1]].Size))//判断子树是否平衡,借鉴了替罪羊树的思想
int rt,ans,tot,cnt,Void[(N<<1)+5];point data[(N<<1)+5];
struct Tree
{
int MaxX,MinX,MaxY,MinY,Size,Son[2];
point p;
Tree(int x=0,int y=0):p(x,y),MaxX(x),MinX(x),MaxY(y),MinY(y){Son[0]=Son[1]=Size=0;}
inline int get_dis(point w) {return abs(p.s[0]-w.s[0])+abs(p.s[1]-w.s[1]);}//求出某个点到当前点的距离
inline int calc(point w) {return max(MinX-w.s[0],0)+max(w.s[0]-MaxX,0)+max(MinY-w.s[1],0)+max(w.s[1]-MaxY,0);}//求出一个点到该子树构成矩形的距离
}node[(N<<1)+5];
inline void PushUp(int x)//上传信息
{
node[x].Size=node[node[x].Son[0]].Size+node[node[x].Son[1]].Size+1,
node[x].MaxX=max(node[x].p.s[0],max(node[node[x].Son[0]].MaxX,node[node[x].Son[1]].MaxX)),node[x].MinX=min(node[x].p.s[0],min(node[node[x].Son[0]].MinX,node[node[x].Son[1]].MinX)),
node[x].MaxY=max(node[x].p.s[1],max(node[node[x].Son[0]].MaxY,node[node[x].Son[1]].MaxY)),node[x].MinY=min(node[x].p.s[1],min(node[node[x].Son[0]].MinY,node[node[x].Son[1]].MinY));
}
inline int Build(int l,int r,int op)//建树
{
register int x=Void[tot--],mid=l+r>>1;
d=op,nth_element(data+l+1,data+mid+1,data+r+1),node[x]=Tree(data[mid].s[0],data[mid].s[1]),
l<mid&&(node[x].Son[0]=Build(l,mid-1,op^1)),r>mid&&(node[x].Son[1]=Build(mid+1,r,op^1));
return (void)(PushUp(x)),x;
}
inline void Traversal(int x) {if(x) Traversal(node[x].Son[0]),data[++cnt]=node[Void[++tot]=x].p,Traversal(node[x].Son[1]);}//中序遍历
inline void ReBuild(int &x,int op) {cnt=0,Traversal(x),x=Build(1,cnt,op);}//重构
inline void ins(int &x,point v,int op)//插入元素
{
if(!x) return (void)(node[x=Void[tot--]]=Tree(v.s[0],v.s[1]),node[x].Size=1);
v.s[op]<node[x].p.s[op]?ins(node[x].Son[0],v,op^1):ins(node[x].Son[1],v,op^1);
PushUp(x);
}
inline void check(int &x,point v,int op)//判断是否平衡
{
if(!x) return;if(!balance(x)) return ReBuild(x,op);
v.s[op]<node[x].p.s[op]?check(node[x].Son[0],v,op^1):check(node[x].Son[1],v,op^1);
}
inline void qry(int x,point v)//询问
{
register int res=node[x].get_dis(v),dis1=node[x].Son[0]?node[node[x].Son[0]].calc(v):INF,dis2=node[x].Son[1]?node[node[x].Son[1]].calc(v):INF;//res求出当前点的距离,dis1和dis2分别求出当前询问点到两个子树构成矩形的距离
ans=min(ans,res);//更新ans
if(dis1<dis2) dis1<ans&&(qry(node[x].Son[0],v),0),dis2<ans&&(qry(node[x].Son[1],v),0);//注意剪枝
else dis2<ans&&(qry(node[x].Son[1],v),0),dis1<ans&&(qry(node[x].Son[0],v),0);
}
public:
Class_TwoDTree() {node[0].MaxX=node[0].MaxY=-INF,node[0].MinX=node[0].MinY=INF;for(register int i=N<<1;i;--i) Void[++tot]=i;}//初始化
inline void Init(int n,point *num) {for(register int i=1;i<=n;++i) data[i]=num[i];rt=Build(1,n,0);}//建树
inline void Insert(point v) {ins(rt,v,0),check(rt,v,0);}//插入元素
inline int Query(point v) {return (void)(ans=INF,qry(rt,v)),ans;}//询问
}TwoDTree;
int main()
{
register int i,n,Q,op,x,y;
for(F.read(n),F.read(Q),i=1;i<=n;++i) F.read(x),F.read(y),s[i]=point(x,y);
for(TwoDTree.Init(n,s);Q;--Q)
{
static int v=0;
F.read(op),F.read(x),F.read(y);
if(op^1) F.write(TwoDTree.Query(point(x,y))),F.write_char('\n'),++v;
else TwoDTree.Insert(point(x,y));
}
return F.end(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒