1. 程式人生 > >【樹形DP】樹的重心

【樹形DP】樹的重心

題目

樹的重心
時間限制: 1 Sec 記憶體限制: 128 MB

題目描述

給出一個連通的無向圖,它有N 個頂點和 N-1 條邊 ,這顯然是一棵樹。現在需要找到這棵樹的重心。
為了定義樹的重心,需要給樹的每一個頂點賦上一個權值。考慮頂點k。如果從圖中刪除k號頂點(連帶的邊也一起被刪除),剩下的圖將只有 N-1 個頂點而且可能由多個連通分量組成。顯然每一個連通分量還是一棵樹。那麼k號頂點的權值就是刪除它以後剩下的連通分量中頂點數最多的頂點個數。
這 N 個頂點中權值最小的就是重心。
例如,在如圖所示的樹中,刪除結點2,整棵樹分離成3個子樹:(3)、(4)和 (1,5,6,7),最大連通分量的值為4
重心示例


如果刪除結點1,樹將分離成2個子樹(2,3,4)和(5,6,7),最大連通分量的值為3
列舉所有結點後發現,刪除1的結果是最小的,因此1是這棵樹的重心。
樹的重點不一定是唯一的。

輸入

第一行是整數 N (1<=N<=16 000)。接下來 N-1 行每行兩個整數, a 和 b,用空格隔開,表示頂點 a 和 頂點 b之間有一條邊。

輸出

第一行輸出兩個整數,最小的權值和重心的個數
第二行按遞增順序輸出這些重心的編號。資料之間用一個空格隔開。

樣例輸入

7
1 2
2 3
2 4
1 5
5 6
6 7

樣例輸出

3 1
1

分析

重心,簡而言之就是在樹上找一點,把樹分成幾部分,使得最大部分最小。

很明顯要知道每個子樹的大小,定義Size(i)表示以i為根的子樹的大小(包括它自己)。於是d(i)=1+Size(i.son)
這樣便可以O(N)遍歷得出d陣列。

得到d以後,列舉每一個點,就能得到這個點刪除後各部分的大小。例如刪除i後,最大部分的大小就是max(Size[i.son],NSize[i])NSize[i]即除去以i為根的子樹剩下的大小。
N-Size[i]

程式碼

#include<cstdio>
#include<vector>
#include<algorithm> using namespace std; #define MAXN 16000 #define INF 0x7fffffff vector<pair<int,bool> > G[MAXN+5]; int Size[MAXN+5],w[MAXN+5]; int N; void GetSize(int u,int fa){ Size[u]=1;//初值(u自己) int sz=G[u].size(); for(int i=0;i<sz;i++){ int v=G[u][i].first; if(v!=fa){ G[u][i].second=1;//second==1表示G[u][i]是u的兒子,反之則是u的父親 GetSize(v,u); Size[u]+=Size[v]; } } } int main(){ scanf("%d",&N); for(int i=1;i<N;i++){ int u,v; scanf("%d%d",&u,&v); G[u].push_back(make_pair(v,0)); G[v].push_back(make_pair(u,0)); } GetSize(1,-1); int Ans=INF; for(int i=1;i<=N;i++){ w[i]=N-Size[i]; int sz=G[i].size(); for(int j=0;j<sz;j++) w[i]=max(w[i],G[i][j].second*Size[G[i][j].first]); //如果G[i][j]是i的父親就不能統計它的Size Ans=min(Ans,w[i]); } printf("%d",Ans); int sum=0; for(int i=1;i<=N;i++) if(w[i]==Ans) sum++; printf(" %d\n",sum); bool f=1; for(int i=1;i<=N;i++) if(w[i]==Ans){ if(f) printf("%d",i),f=0; else printf(" %d",i); }//輸出重心 }