【JZOJ 4639】 Angel Beats!

Description

天使立华奏攻入了死后世界战线(SSS)的地下工会Guild,这是万分危急的时候。仲村由理指挥工会成员有条不紊地进行撤退工作。工会成员在Guild最深层工厂安放炸药需要很长的准备时间,需要有人来拖延立华奏的前进速度。但是他们并不清楚立华奏的具体位置,因此他们需要设立许多个防御点。
Guild的结构可以看成一棵有n 个节点的树,有时由理会得到立华奏的大概位置,可能在某两棵子树的任意一棵中,她就会找到Guild树(不一定要在两棵子树内)上的一个点,使得该点到两棵子树中所有点距离之和最小,即这两棵子树的重心(如果两棵子树有重合部分,那么取它们并集求重心)。
具体而言,你会得到Guild的结构(1为根),然后会有q个询问,向你查询点x子树和点y子树的重心,重心可能会有很多个,你只需要输出距离和即可。

Analysis

2017 5月:光荣入AB,成为伪团长铁粉=w=
crazy大佬的神题!

结论 1:两棵子树的重心一定在两棵子树各自重心连线的路径上。

嗯,感性理解起来是显然的。

结论 2:在两棵子树size较大那棵内部一定能找到两棵子树重心。

还是感性理解。当然,证明是由结论一,让重心在两颗子树的重心连线路径上调整必能更优。

结论 2 的推论:对于一个点x,令其size最大儿子为y,如果size[y]<=size[x]-size[y],那么子树x的重心一定为x,否则就一定在点y的子树内。

有了这个,直接从重心往上倍增。
疯狂的结论QAQ!
求每个子树的重心,可以用正难则反的思想。
子树所有点到x距离和=所有点到x距离和-非子树所有点到x距离和
维护几个东西,乱搞一下。
然后一棵树重心可能有两个,而我们求的总是较深的那一个,所以另一个重心(此时就是它的父亲)也要拉去讨论一下(好像不这么干会错)

Code

具体的实现细节极极极极多,直追GDOI的疯狂动物城。
我用对拍拍出了至少10个小错误。

#include<cstdio>
#include<cmath>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,b,a) for(int i=b;i>=a;i--)
#define efo(i,v) for(int i=last[v];i;i=next[i])
using namespace std;
const int N=100010,M=2*N;
int n,tot,to[M],next[M],last[N],f[N][20],dep[N],size[N],sum[N],val[N],focus[N];
void link(int u,int v)
{
    to[++tot]=v,next[tot]=last[u],last[u]=tot;
}
void dfs(int v,int from,int d)
{
    size[v]=1,dep[v]=d;
    efo(i,v)
    {
        int u=to[i];
        if(u==from) continue;
        dfs(u,v,d+1);
        size[v]+=size[u],sum[v]+=sum[u];
    }
    sum[v]+=size[v]-1;
}
int getlca(int u,int v)
{
    if(dep[u]<dep[v]) swap(u,v);
    fd(i,log2(dep[u]),0)
        if(dep[f[u][i]]>=dep[v]) u=f[u][i];
    fd(i,log2(dep[u]),0)
        if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
    if(u!=v) return f[u][0];
    return u;
}
int dis(int u,int v)
{
    return dep[u]+dep[v]-2*dep[getlca(u,v)];
}
int all(int x,int y)
{
    if(getlca(x,y)==y) return val[x]-(val[y]-sum[y])-(n-size[y])*dis(x,y);
    else return sum[y]+dis(x,y)*size[y];
}
int up(int v,int d,int p)
{
    fd(i,log2(dep[v]),0)
        if(f[v][i] && dep[f[v][i]]>=d && size[f[v][i]]<p-size[f[v][i]]) v=f[v][i];
    return v;
}
void dfs1(int v,int from)
{
    int k=0,sum=0;
    efo(i,v)
    {
        int u=to[i];
        if(u==from) continue;
        val[u]=val[v]-size[u]+n-size[u];
        dfs1(u,v);
        if(size[u]>size[k]) k=u;
        sum+=size[u];
    }
    if(size[k]<=sum-size[k]) focus[v]=v;
    else
    {
        focus[v]=up(focus[k],dep[v],sum+1);
        if(all(f[focus[v]][0],v)<all(focus[v],v)) focus[v]=f[focus[v]][0];
    }
}
int main()
{
    scanf("%d",&n);
    fo(i,2,n)
    {
        scanf("%d",&f[i][0]);
        link(i,f[i][0]),link(f[i][0],i);
    }
    fo(j,1,log2(n))
        fo(i,1,n) f[i][j]=f[f[i][j-1]][j-1];
    dfs(1,0,1);
    val[1]=sum[1];
    dfs1(1,0);
    int _,x,y,u;
    scanf("%d",&_);
    while(_--)
    {
        scanf("%d %d\n",&x,&y);
        int lca=getlca(x,y);
        if(x==lca)
        {
            printf("%d\n",all(focus[x],x));
            continue;
        }
        if(y==lca)
        {
            printf("%d\n",all(focus[y],y));
            continue;
        }
        int num=size[x]+size[y];
        if(size[x]>size[y]) u=up(focus[x],dep[x],num);
        else u=up(focus[y],dep[y],num);
        printf("%d\n",min(all(u,x)+all(u,y),all(f[u][0],x)+all(f[u][0],y)));
    }
    return 0;
}

版权声明:本文为lyd_7_29原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。