【JZOJ 4639】Angel Beats!

Description

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

抽象题意:给出一棵树,求两颗子树x,y的重心和所有子树内的点到重心的距离。

Solution

设每个点的儿子总数为 si ,所有儿子到点i的距离为 sli
首先有一个结论,同一个祖先q的儿子进行合并,如果重心不是当前的祖先,就只可能在 2sk>=si 的儿子k上,而且只可能在原儿子重心到q的路径上,

再运用点分治的找重心方法,即 max(sk)(kq) 最小的点为重心,
那么加上上文的结论,不停的从儿子的重心往上跳,直到 max(sk)(kq)>=q 左右为止,
左右的意思是我们需要把我们找到的这个点求一遍最优值后,把它往来路退一格,因为退一格的情况依旧有可能是重心,
以上的操作可以用倍增来实现,

还有一个结论:两颗无交集的子树,重心一定在较大的那一棵上,(这个显然吧)
同样的对于子树x,y(子树x>子树y),我们需要在点x到它的重心上找一点作为两颗子树的重心,(感性的理解一下)
这个跟上面的差不多,可以也用倍增;
有交集的直接输出即可;
计算距离的时候预处理一下全部点到每个点距离和子树内的点到祖先的距离即可;
复杂度: O(2nlog(n)+mlog(n))

Code

有点恶心…

#include<iostream>
#include<cstdio>
#include<cstdlib>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define efo(i,q) for(int i=A[q];i;i=B[i][0])
#define iff() if(B[i][1]!=fa)
#define LCA(q,w) (LCA2(LCA1(q,a[w].c),LCA1(w,a[q].c)))
#define H(q,w) (a[q].sl+1)
using namespace std;
const int N=100500,M=20;
int read(int &n)
{
    char ch=' ';int q=0,w=1;
    for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
    if(ch=='-')w=-1,ch=getchar();
    for(;ch>='0' && ch<='9';ch=getchar())q=q*10+ch-48;n=q*w;return n;
}
int n,m,ans;
struct qqww
{int s,c,h,mx,hl,sl,ul;}a[N];
int g[N][M+1];
int B[2*N][2],A[N],B0;
void link(int q,int w)
{B[++B0][0]=A[q],A[q]=B0,B[B0][1]=w;}
int dfs1(int q,int c,int fa)
{
    a[q].s=1;g[q][0]=fa;a[q].c=c;
    efo(i,q)iff()
    {
        a[q].s+=dfs1(B[i][1],c+1,q);
        a[q].sl+=a[B[i][1]].sl;
    }
    a[q].sl+=a[q].s-1;
    return a[q].s;
}
void dfsj(int q,int fa,int s)
{
    a[q].ul=s;
    efo(i,q)iff()
    {
        int w=B[i][1];
        dfsj(w,q,s+a[q].sl-a[w].sl-a[w].s+n-a[w].s);
    }
}
int LCA1(int q,int w)
{
    int i;
    while(a[q].c>w)
    {
        for(i=0;a[g[q][i+1]].c>w;i++);
        q=g[q][i];
    }
    return q;
}
int LCA2(int q,int w)
{
    int i;
    while(q!=w)
    {
        for(i=0;g[q][i+1]!=g[w][i+1];i++);
        q=g[q][i];w=g[w][i];
    }
    return q;
}
int JP(int q,int w,int tp)
{
    int I;
    while(w-a[q].s>a[q].mx&&a[q].c>a[tp].c)
    {
        for(I=0;w-a[g[q][I+1]].s>a[g[q][I+1]].mx&&a[g[q][I+1]].c>a[tp].c;)
         I++;
        q=g[q][I];
    }
    return q;
}
void dfs2(int q,int fa)
{
    int s=0,w;a[q].h=q;a[q].hl=a[q].sl;
    efo(i,q)iff()dfs2(B[i][1],q),s=max(s,a[B[i][1]].s);
    a[q].mx=s;
    if(a[q].s==1)return;
    efo(i,q)iff()if(a[B[i][1]].s*2>=a[q].s)
    {
        w=JP(a[B[i][1]].h,a[q].s,q);
        w=LCA1(a[B[i][1]].h,a[w].c+1);
        if(max(a[w].mx,a[q].s-a[w].s)<s)
        {
            a[q].h=w,s=max(a[w].mx,a[q].s-a[w].s);
            a[q].hl=a[w].sl+(a[q].s-a[B[i][1]].s)*(a[w].c-a[q].c)+a[w].ul-(a[q].ul+(n-a[B[i][1]].s)*(a[w].c-a[q].c));
        }
        w=g[w][0];
        if(max(a[w].mx,a[q].s-a[w].s)<s)
        {
            a[q].h=w,s=max(a[w].mx,a[q].s-a[w].s);
            a[q].hl=a[w].sl+(a[q].s-a[B[i][1]].s)*(a[w].c-a[q].c)+a[w].ul-(a[q].ul+(n-a[B[i][1]].s)*(a[w].c-a[q].c));
        }
    }
}
int main()
{
    int q,w,e,_;
    read(n);
    fo(i,2,n)read(q),link(q,i);
    dfs1(1,1,0);
    fo(j,1,M)fo(i,1,n)g[i][j]=g[g[i][j-1]][j-1];
    dfsj(1,0,0);
    dfs2(1,0);
    read(_);
    while(_--)
    {
        read(q),read(w);
        if(a[q].s<a[w].s)swap(q,w);
        e=LCA(q,w);
        if(e==q)
        {
            printf("%d\n",a[q].hl);
        }else 
        {
            int t=JP(a[q].h,a[q].s+a[w].s,q);
            ans=a[t].sl+(a[w].sl+a[w].s*(a[w].c+a[t].c-2*a[e].c))+(a[t].ul-(a[q].ul+(n-a[q].s)*(a[t].c-a[q].c)));
            t=LCA1(a[q].h,a[t].c+1);
            ans=min(ans,a[t].sl+(a[w].sl+a[w].s*(a[w].c+a[t].c-2*a[e].c))+(a[t].ul-(a[q].ul+(n-a[q].s)*(a[t].c-a[q].c))));
            printf("%d\n",ans);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值