CSP2019Day2T3 洛谷P5666:树的重心 (树上倍增)

题目传送门:https://www.luogu.org/problem/P5666


很早以前就觉得,凡是考树的重心相关的题,到最后都变成一道模拟题。

树的重心有许多优秀的性质,比如:
结论一:记f(node)表示以node为根的最大子树的大小。从无根树上的任意一个点x出发,向相邻的点走一步。假设某个点y的f值比x的小,那么x向y走就相当于向树的重心移动了一步。假设不存在这样的y,那么x就是重心。假设存在f(y)=f(x),那么它们就是树的两个重心。
结论二:设全树为有根树。记g(node)表示node为根的子树的重心。假设son是node的子树中size最大的儿子(重儿子),那么g(node)一定是g(son)的祖先,因此让g(son)向上跳即可。这个很显然。
由结论二可得结论三:g(node)一定在node所在的重链上。

基于上述三条结论,很容易得出一个从任意一点x走到当前全树的重心的快速算法:
x先向上跳,直到跳到一个y,使得y上方的size大于y的最大子树的size,而father(y)并不。要注意的是y可能并不存在,而此时x本身就是我们要找的。
接下来就是沿着father(y)或x的重链向下跳。但这样很麻烦,简单的方法是拿出father(y)/x的重儿子的重心向上跳。

那么对于这道题而言,整棵树砍掉node的子树之后的重心,就是从node的父亲重新向上跳,然后再向下跳(或者计算新的重儿子,从它的重心向上跳)。实际处理起来细节比较多,尤其是可能出现y和father(y)本身就是两个重心之类的极端情况。

然后xjb写,xjb调,就过了。


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;

const int maxn=300100;
const int maxl=22;
typedef long long LL;

struct edge
{
    int obj;
    edge *Next;
} e[maxn<<1];
edge *head[maxn];
int cur;

int fa[maxn][maxl];
int dep[maxn];

int st[maxn];
int ed[maxn];
int Time;

int Size[maxn];
int son1[maxn];
int son2[maxn];
int w[maxn];

LL ans;
int n,t;

void Add(int x,int y)
{
    cur++;
    e[cur].obj=y;
    e[cur].Next=head[x];
    head[x]=e+cur;
}

void Update(int son,int node)
{
    if (Size[son]>=Size[ son1[node] ])
    {
        son2[node]=son1[node];
        son1[node]=son;
    }
    else
        if (Size[son]>Size[ son2[node] ])
            son2[node]=son;
}

int Jump(int node,int sum,int limit)
{
    for (int j=maxl-1; j>=0; j--)
    {
        int x=fa[node][j];
        if (dep[x]<limit) continue;
        if (sum-Size[x]>=Size[ son1[x] ]) node=x;
    }
    return node;
}

int Get(int node,int sum)
{
    return max(sum-Size[node],Size[ son1[node] ]);
}

void Dfs(int node)
{
    st[node]=++Time;
    dep[node]=dep[ fa[node][0] ]+1;
    for (int j=1; j<maxl; j++) fa[node][j]=fa[ fa[node][j-1] ][j-1];
    Size[node]=1;

    for (edge *p=head[node]; p; p=p->Next)
    {
        int son=p->obj;
        if (son!=fa[node][0])
        {
            fa[son][0]=node;
            Dfs(son);
            Size[node]+=Size[son];
            Update(son,node);
        }
    }

    ed[node]=Time;
    if (!son1[node]) w[node]=node,ans+=node;
    else
    {
        int x=w[ son1[node] ];
        x=Jump(x,Size[node],dep[node]);
        if ( x!=node && Get(fa[x][0],Size[node])<Get(x,Size[node]) ) x=fa[x][0];
        w[node]=x;
        if (node!=1) ans+=x;
        if ( node!=1 && x!=node && Get(fa[x][0],Size[node])==Get(x,Size[node]) ) ans+=fa[x][0];
    }
}

int Calc(int x,int dec)
{
    int y=son1[x];
    if ( st[y]<=st[dec] && st[dec]<=ed[y] ) return max(Size[y]-Size[dec],Size[ son2[x] ]);
    else return Size[y];
}

int Jump(int node,int sum,int limit,int dec)
{
    for (int j=maxl-1; j>=0; j--)
    {
        int x=fa[node][j];
        if (dep[x]<limit) continue;
        if (sum-Size[x]>= Calc(x,dec) ) node=x;
    }
    return node;
}

struct data
{
    int u,v;
} a[4];

bool Comp(data x,data y)
{
    return x.v<y.v;
}

void Work(int node)
{
    LL tp=ans;

    int x=fa[node][0];
    x=Jump(x,n,1,node);
    //if (node==4) printf("%d\n",x);
    int y=fa[x][0];

    for (int i=0; i<4; i++) a[i].v=100000000;
    a[0].u=x;
    a[0].v=max(n-Size[x],Calc(x,node));
    if (y)
    {
        a[1].u=y;
        a[1].v=max(n-Size[y],Calc(y,node));
    }

    if ( x!=1 && n-Size[x]> Calc(x,node) ) x=y;
    int p=son1[x];
    if ( st[p]<=st[node] && st[node]<=ed[p] )
        if (!son2[x]) p=0;
        else p=son2[x];
    
    if (p)
    {
        p=w[p];
        p=Jump(p,n-Size[node],dep[x]+1);
        a[2].u=p;
        a[2].v=Get(p,n-Size[node]);

        p=fa[p][0];
        if (p!=x)
        {
            a[3].u=p;
            a[3].v=Get(p,n-Size[node]);
        }
    }

    sort(a,a+4,Comp);

    //if (node==4)
        //for (int i=0; i<4; i++) printf("%d %d\n",a[i].u,a[i].v);

    ans+=a[0].u;
    if (a[0].v==a[1].v) ans+=a[1].u;

    //printf("%I64d\n",ans-tp);
}

int main()
{
    scanf("%d",&t);
    while (t--)
    {
        scanf("%d",&n);

        cur=-1;
        for (int i=1; i<=n; i++) head[i]=NULL,son1[i]=son2[i]=0;
        for (int i=1; i<n; i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            Add(x,y);
            Add(y,x);
        }

        Time=0;
        ans=0;
        Dfs(1);

        //printf("%I64d\n",ans);

        for (int i=2; i<=n; i++) Work(i);

        //for (int i=1; i<=n; i++) printf("%d %d ",son1[i],son2[i]);
        //printf("\n");

        printf("%lld\n",ans);
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值