1. 程式人生 > >【POJ - 1655】Balancing Act (樹的重心)

【POJ - 1655】Balancing Act (樹的重心)

Consider a tree T with N (1 <= N <= 20,000) nodes numbered 1...N. Deleting any node from the tree yields a forest: a collection of one or more trees. Define the balance of a node to be the size of the largest tree in the forest T created by deleting that node from T. 
For example, consider the tree: 


Deleting node 4 yields two trees whose member nodes are {5} and {1,2,3,6,7}. The larger of these two trees has five nodes, thus the balance of node 4 is five. Deleting node 1 yields a forest of three trees of equal size: {2,6}, {3,7}, and {4,5}. Each of these trees has two nodes, so the balance of node 1 is two. 

For each input tree, calculate the node that has the minimum balance. If multiple nodes have equal balance, output the one with the lowest number. 

Input

The first line of input contains a single integer t (1 <= t <= 20), the number of test cases. The first line of each test case contains an integer N (1 <= N <= 20,000), the number of congruence. The next N-1 lines each contains two space-separated node numbers that are the endpoints of an edge in the tree. No edge will be listed twice, and all edges will be listed.

Output

For each test case, print a line containing two integers, the number of the node with minimum balance and the balance of that node.

Sample Input

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

Sample Output

1 2

題意:

n個結點的一棵樹,去掉某個結點x後,會變成一個森林。這個森林中的每棵樹都有自己的結點數,這個森林中所有樹中最大的結點數稱作刪掉結點x的餘額,求刪除哪一個結點的餘額最小,並輸出該節點的編號和最小值。

思路:

這一個題就是求樹的重心,首先是用鄰接表存雙向邊(一定是雙向的,單向的不對)。然後以任意一個點為根將無向圖變成有向圖,然後進行dfs,找出刪掉每個節點後,生成的森林中樹的最大的結點個數(即本題要求的餘額),迴圈遍歷一遍找到最小值,並記錄下標(樹的編號)。

關於樹是加單向邊還是雙向邊,我覺得具體看題目要求,如果題目中說明了,兩個點一個是根節點一個是子節點,那麼一般就加單向邊,並且搜尋的時候要從根節點開始搜尋,就如一個公司中上下級關係,加邊時候說了,一個上司一個下屬。但是如果題目並沒有明確說明,而是就給了一個圖,說加邊後是一顆樹,這種情況可能就是要加雙向邊,就如本題,因為如果本題是加雙向邊的話,題中只給出了兩個點是有邊的,只加單向邊,那麼就只能從一段掃,從另一端就掃不到,這樣,你必須要找一個根節點,然後從根節點開始搜尋,像這本題,加單向邊,隨便找一個點開始掃,這樣某兩個點可能是連通的,但選根節點不好,搜尋到這個點,發現沒有子節點了(其實應該有,從另一邊搜尋能搜到,但這一邊搜尋不到),然後就返回了,結果就不對了。

求根節點的話,可以記錄每個點的入度,求入度為0的點。

或者在明確的告訴上下級的關係,並且每個點的編號從1~n,那麼我們可以先求出1~n的和(即root=(1+n)*n/2)然後每次輸入一對點,把子節點(假設編號為i)減掉,就是root-=i;最後root的值就是根節點的值。

樹的重心的定義:

對於一棵樹n個節點的無根樹,找到一個點,使得把樹變成以該點為根的有根樹時,最大子樹的結點數最小。換句話說,刪除這個點後最大連通塊(一定是樹)的結點數最小。

性質:

  1. 樹中所有點到某個點的距離和中,到重心的距離和是最小的,如果有兩個距離和,他們的距離和一樣。

  2. 把兩棵樹通過一條邊相連,新的樹的重心在原來兩棵樹重心的連線上。

  3. 一棵樹新增或者刪除一個節點,樹的重心最多隻移動一條邊的位置。

  4. 一棵樹最多有兩個重心,且相鄰。

ac程式碼:

#include<stdio.h>
#include<iostream>
#include<map>
#include<string.h>
#define ll long long
#define mod 1000000007
using namespace std;
struct node{
	int y;
	int nex;
}side[20100*2];//因為是加雙向邊所以要開雙倍的空間 
int head[20100];
int cnt=0;
int n;
void init()
{
	cnt=0;
	memset(head,-1,sizeof(head));
}
void add(int x,int y)
{
	side[cnt].y=y;
	side[cnt].nex=head[x];
	head[x]=cnt++;
}
int dp[20100],num[20100];//dp陣列表示第i個結點的餘額是多少,num表示第i個結點他往下的子節點的數量,這個是求得第i個結點分支的餘額是多少,為了求他父節點準備的。 
void dfs(int x,int f)//x當前結點,f是x的父節點 
{
	dp[x]=0;num[x]=1;//Num為1應該是把當前結點算上 
	for(int i=head[x];i!=-1;i=side[i].nex) 
	{
		int ty=side[i].y;
		if(ty==f)//只向子結點掃,父節點跳過 
		continue;
		dfs(ty,x);
		dp[x]=max(dp[x],num[ty]);//更新這一個點的最大餘額 
		num[x]+=num[ty];//計算該點及其子結點總共的個數,為求x得父節點的最大餘額做準備
	}
	dp[x]=max(dp[x],n-num[x]);
	/*一個點的最大餘額是把該結點刪掉後,生成的所有不連通的樹的結點個數的最大值。
	因此這裡是要判斷除了他的子節點外的所有結點的數量(這一部分是一棵樹,不過結點都不是x的子結點)是不是比子節點的最優解還要大。 
	*/ 
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		init();
		scanf("%d",&n);
		int u,v; 
		for(int i=0;i<n-1;i++)
		{
			scanf("%d%d",&u,&v);
			add(u,v);//必須加雙向邊 
			add(v,u);
		}
		dfs(1,-1);
		int ans=dp[1],pos=1;//設1為最大值 
		for(int i=2;i<=n;i++)
		{
			if(ans>dp[i])//因為要求數量相同時編號儘量的小所以不能有等於號 
			{
				ans=dp[i];
				pos=i;
			}
		}
		printf("%d %d\n",pos,ans);
	}
	return 0; 
};