樹的重心

題目

題目鏈接

 

題解:

/*
這道題要用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" );
    }

}

 

發佈了70 篇原創文章 · 獲贊 7 · 訪問量 4135
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章