题解 P4169 【[Violet]天使玩偶/SJY摆棋子】
KD-Tree优化
相信 \(\text{KD-Tree}\) 大家都会吧不会还来看这篇题解么,但普通的 \(\text{KD-Tree}\) 并不能AC此题。所以我来讲一些优化。
1.建树优化
众所周知,\(\text{KD-Tree}\) 是维护切割空间的数据结构,为了保持树高在 \(\log n\) 范围,那么要怎么取切的点和切割方向呢?
先判断切割方向。我们要满足其维度的点差异度大,那么能维护的空间就更大了。差异度大就是方差大,所以计算两个维度的方差比较即可。
然后选切割的点——当然是中位数。我们可以用STL的nth_element来 \(\mathcal O(n)\) 求出中位数并把比它小的数放它左边,比它大的数放在右边(即未完成的快排),然后递归建树即可。
Code:
il void pushup(int u){
mxx[u]=max(mxx[u],mxx[now]);mxy[u]=max(mxy[u],mxy[now]);
mnx[u]=min(mnx[u],mnx[now]);mny[u]=min(mny[u],mny[now]);
}
il bool cmp(node p,node q){
if(dcmp)return p.x<q.x;
return p.y<q.y;
}
int build(int l,int r){
if(l>r)return 0;
int mid=(l+r)/2;
double avx=0,avy=0,vax=0,vay=0;
for(int i=l;i<=r;i++)
avx+=a[i].x,avy+=a[i].y;
avx/=1.0*(r-l+1);avy/=1.0*(r-l+1);
for(int i=l;i<=r;i++)
vax+=sqr(a[i].x-avx),vay+=sqr(a[i].y-avy);
dcmp=d[mid]=(vax>vay);nth_element(a+l,a+mid,a+r+1,cmp);
ls[mid]=build(l,mid-1);rs[mid]=build(mid+1,r);
mxx[mid]=mnx[mid]=a[mid].x;
mxy[mid]=mny[mid]=a[mid].y;
if(ls[mid])now=ls[mid],pushup(mid);
if(rs[mid])now=rs[mid],pushup(mid);
return mid;
}
2.重构优化
即使有了建树优化,我们插入时也只能轮换分割,可能导致树高远远大于 \(\log n\) 级别,那么在子树大小过大时,我们就要把这一部分重新分配,进行重构,即重新部分建树。
Code:
il void pushup(int u){
mxx[u]=max(mxx[u],mxx[now]);mxy[u]=max(mxy[u],mxy[now]);
mnx[u]=min(mnx[u],mnx[now]);mny[u]=min(mny[u],mny[now]);
}
il double sqr(double x){return x*x;}
bool dcmp;
int g[maxn],tot;
il bool cmp(int p,int q){
if(dcmp)return a[p].x<a[q].x;
return a[p].y<a[q].y;
}
int build(int l,int r){
if(l>r)return 0;
int mid=(l+r)/2;
double avx=0,avy=0,vax=0,vay=0;
for(int i=l;i<=r;i++)
avx+=a[g[i]].x,avy+=a[g[i]].y;
avx/=1.0*(r-l+1);avy/=1.0*(r-l+1);
for(int i=l;i<=r;i++)
vax+=sqr(a[g[i]].x-avx),vay+=sqr(a[g[i]].y-avy);
dcmp=d[g[mid]]=(vax>vay);nth_element(g+l,g+mid,g+r+1,cmp);
ls[g[mid]]=build(l,mid-1);rs[g[mid]]=build(mid+1,r);
mxx[g[mid]]=mnx[g[mid]]=a[g[mid]].x;
mxy[g[mid]]=mny[g[mid]]=a[g[mid]].y;
siz[g[mid]]=1;
if(ls[g[mid]])now=ls[g[mid]],pushup(g[mid]),siz[g[mid]]+=siz[ls[g[mid]]];
if(rs[g[mid]])now=rs[g[mid]],pushup(g[mid]),siz[g[mid]]+=siz[rs[g[mid]]];
return g[mid];
}
void print(int u){//中序遍历
if(!u)return;
print(ls[u]);
g[++tot]=u;
print(rs[u]);
}
il void rebuild(int &u){
tot=0;print(u);
u=build(1,tot);
}
il bool bad(int u){
return 0.725*siz[u]<=1.0*max(siz[ls[u]],siz[rs[u]]);
}
void ins(int &u,bool op){//在子树u中插入
if(!u)return u=now,siz[u]=1,void();
if(!op)ins(a[now].x<=a[u].x?ls[u]:rs[u],!d[u]);
else ins(a[now].y<=a[u].y?ls[u]:rs[u],!d[u]);
pushup(u);siz[u]=siz[ls[u]]+siz[rs[u]]+1;
int tmp=now;if(bad(u))rebuild(u);now=tmp;
}
3.查询优化
剪枝就不用我说了吧,\(\text{KD-Tree}\) 必备。
Code:
il int dis(int u){
return abs(a[now].x-a[u].x)+abs(a[now].y-a[u].y);
}
il int mindis(int u){//期望最小距离
int res=0;
if(mnx[u]>a[now].x||a[now].x>mxx[u])res+=mnx[u]>a[now].x?mnx[u]-a[now].x:a[now].x-mxx[u];
if(mny[u]>a[now].y||a[now].y>mxy[u])res+=mny[u]>a[now].y?mny[u]-a[now].y:a[now].y-mxy[u];
return res;
}
void ask(int u){
if(!u)return;
ans=min(ans,dis(u));
int l=mindis(ls[u]),r=mindis(rs[u]);
if(l<r){
if(l<ans)ask(ls[u]);
if(r<ans)ask(rs[u]);
}else{
if(r<ans)ask(rs[u]);
if(l<ans)ask(ls[u]);
}
}

浙公网安备 33010602011771号