树的重心

题目

题目链接

 

题解:

/*
这道题要用dp+倍增,也就是倍增dp
首先要了解树的重心的基本性质
1.重心都是相邻的
2.重心都是在树的重边上(不会证明)
那么就可以dp了,dp[i][j]表示从i节点开始,向下跳2的j次方条重边所得到的点,转移和LCA是一样的
现在要考虑换根,其实可以在线维护,具体看代码
*/
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <iostream>
#define ll long long
#define reg register
using namespace std;
const int MAXN = 399995;
int a[MAXN];
int f[MAXN][23] , s1[MAXN] , s2[MAXN] , s3[MAXN] , sz[MAXN];//s1是原树每个点的重儿子,s2是次重儿子
ll ans;//s3存当前树(换根后)每个点的重儿子,sz是原树的每个点包含的节点数,sum是现在的树每个点包含的节点数
int sum[MAXN];
vector<int>G[MAXN];
int fa[MAXN];
int n;
inline void read( int &x ){
    x = 0;char s = getchar();
    while( s < '0' || s > '9' ) s = getchar();
    while(s >= '0' && s <= '9' ){
        x = x *10 + s - '0' , s = getchar();
    }
}
inline void write( ll x ){
    if( x > 9 )
        write( x / 10 );
    putchar( ( x % 10) + '0' );
}
inline void dfs( int x , int pa ){//这个就是简单的预处理,没什么难度
    sz[x] = 1;
    for( reg int i = 0 ; i < G[x].size() ; i ++ ){
        int v = G[x][i];
        if( v == pa ) continue;
        fa[v] = x;
        dfs( v , x ) ;
        sz[x] += sz[v];
        if( sz[v] > sz[s1[x]] )
            s2[x] = s1[x] , s1[x] = v;
        else if( sz[v] > sz[s2[x]] )
            s2[x] = v;
    }
    f[x][0] = s1[x];
    for( int i = 1 ; i <= 20 ; i ++ )
        f[x][i] = f[f[x][i-1]][i-1];
}
inline ll suan( int x , int mu ){
    ll tot = 0;
    if( max( sum[s3[x]] , mu - sum[x] ) <= mu / 2 )
        tot += x;
    return tot;
}
inline void dfs2( int x , int pa ){
    for( reg int i = 0 ; i < G[x].size() ; i ++ ){
        int v = G[x][i];
        if( v == pa ) continue;
        sum[x] = sz[1] - sz[v] , sum[v] = sz[v];fa[x] = fa[v] = 0;//断掉(x,v)这条边
        int k;
        if( v == s1[x] )
            s3[x] = s2[x];
        else
            s3[x] = s1[x];//计算重儿子,注意x的父亲也成为x的子节点,也要包含进去
        if( sum[pa] > sum[s3[x]] ) s3[x] = pa;
        f[x][0] = s3[x];//因为前面的点都是已经更新好了的,可以直接进行转移
        for( reg int j = 1 ; j <= 20 ; j ++ )
            f[x][j] = f[f[x][j-1]][j-1];
        k = x;
        for( reg int j = 20 ; j >= 0 ; j -- ){
            if( f[k][j] && sum[x] - sum[f[k][j]] <= sum[x] / 2 ) k = f[k][j];
        }//如果它满足重心的一个性质,那么希望它的sum[]值越小越好
        ans += suan( k , sum[x] ) + suan( fa[k] , sum[x] );//计算
        k = v;
        for( reg int j = 20 ; j >= 0 ; j -- ){
            if( f[k][j] && sum[v] - sum[f[k][j]] <= sum[v] / 2 ) k = f[k][j];
        }
        ans += suan( k , sum[v] ) + suan( fa[k] , sum[v] );
        fa[x] = v;
        dfs2( v , x );
    }
    s3[x] = s1[x] , sum[x] = sz[x];
    fa[x] = pa , f[x][0] = s1[x];
    for( reg int j = 1 ; j <= 20 ; j ++ )
        f[x][j] = f[f[x][j-1]][j-1];
}
int main(){
    int t;
    read( t );
    while( t -- ){
        read( n );
        for( reg int i = 1 ; i <= n ; i ++ ){
            s1[i] = s2[i] = s3[i] = sz[i] = fa[i] = 0;
            sum[i] = 0;
            G[i].clear();
        }
        ans = 0;
        for( reg int i = 1 ; i < n ; i ++ ){
            int x , y;read( x );read( y );
            G[x].push_back( y );
            G[y].push_back( x );
        }
        dfs( 1 , 0 );
        for( int i = 1 ; i <= n ; i ++ )
            sum[i] = sz[i] , s3[i] = s1[i];//赋值
        dfs2( 1,  0 );//开始换根
        write( ans );
        printf( "\n" );
    }

}

 

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页