问题描述
树的重心是,当去掉该结点后,树的各个连通分量中,结点数最多的连通分量的结点数达到最小值。
例如,有由1~7号结点组成的一棵树,
如果选1号结点做重心,左边的连通分量有2、4、5共3个结点,右边的连通分量有3、6、7共3个结点,结点数最多的连通分量有3个结点;
如果选2号结点做重心,那么存在三个连通分量,分别有4一个结点、5一个结点以及1、3、6、7四个结点,所以结点数最多的连通分量有4个结点;
以此类推,可以得出应当将1号结点作为重心的结论,因为这种情况下各连通分量中结点数最多的连通分量含有3个节点,节点数最少。
输入:
第一行:一个整数n,表示树的结点个数(n<100);
接下来n-1行,每行两个数i,j,表示i和j有边相连。
样例输入:
7
1 2
1 3
2 4
2 5
3 6
3 7
输出:
第一行:一个整数k,表示重心的个数。
接下来K行,每行一个整数,表示重心,按从小到大的顺序给出。
样例输出:
1
1
- 不同于其它的树形DP,这一题是先DFS,再DP
(之前都是树形与DP一步到位)。
状态建模:
p[i]为去掉i点与它相连的边之后,图中所存在的子树中的结点数最大的值。将p数组比较,最小的就是树的重心。
状态转移:
再去掉点后,它的儿子就会独立成为子树,结点数就是遍历各个子树获得子树的结点数。
还有一棵由原根节点为根的树,它的结点数就是原树结点个数-(被取点的所有子树结点个数之和+1(当前结点))。
再逐一比较各个连通分量的结点个数,最大的就是p[i]了。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
int n,m,son[105],ans,id[105],len,p[105],lenp;
vector<int>G[105];// G[i]为i结点的所有子结点
bool v[105];// 是否是空结点
int dfs(int x)// 统计子结点x所在子树含有的结点数目
{
if(!G[x].size())
return 1;//结点x没有子结点,所在子树只有结点x一个结点
int sum=0;
for(int i=0;i<G[x].size();i++)
// G[x][i]表示当前结点的第x个子结点的第i个子结点
{
if(!v[G[x][i]])//当前结点的第x个子结点的第i个子节点的自身及后辈结点数量
{
v[G[x][i]]=1;
sum+=dfs(G[x][i]);
}
//为什么直接这样写不可以?sum=sum+dfs(G[x][i]);因为G(最底层结点)不存在,if(!G(x).size()||G(x)==null)
}
return sum+1;
}
int main()
{
scanf("%d",&n);// 共有n个结点
int a,b;
for(int i=1;i<n;i++)
{
scanf("%d%d",&a,&b);// 输入各个边
G[a].push_back(b);
}
for(int i=1;i<=n;i++)
{
memset(v,0,sizeof(v));
v[i]=1;
son[i]=dfs(i);// son[i]表示第i个结点自身及所有后辈结点的数量
}
int minn=0x3f3f3f3f;
//0x3f3f3f3f的十进制是1061109567,是10^9级别的(和0x7fffffff一个数量级),而一般场合下的数据都是小于10^9的,所以它可以作为无穷大使用而不致出现数据大于无穷大的情形。
for(int i=1;i<=n;i++)
{
int maxn=n-son[i];
for(int j=0;j<G[i].size();j++)
maxn=max(maxn,son[G[i][j]]);//son[G[i][j]]第i个结点的第j个后辈所在子树结点数量,maxn表示结点i的所有连通分量包含最多结点数
minn=min(maxn,minn);// 所有结点中的最小值
p[i]=maxn;//结点i的所有连通分量包含最多结点数
}
for(int i=1;i<=n;i++)
{
if(p[i]==minn)
{
lenp++;// 重心的个数
id[lenp]=i;//重心下标
// 保证了重心下标是从小到大排列的
}
}
printf("%d\n",lenp);
for(int i=1;i<=lenp;i++)
printf("%d\n",id[i]);
}