题解 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]);
    }
}

完整代码:https://www.luogu.com.cn/paste/9z0gpvy2

posted @ 2020-12-27 00:37  ADay526  阅读(207)  评论(0)    收藏  举报