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

版权声明:如果有什么问题,欢迎大家指出,希望能和大家一起讨论、共同进步。 https://blog.csdn.net/QQ_774682/article/details/84067181

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; 
};

猜你喜欢

转载自blog.csdn.net/QQ_774682/article/details/84067181