动态规划——树形DP之树的重心

问题描述

树的重心是,当去掉该结点后,树的各个连通分量中,结点数最多的连通分量的结点数达到最小值。
例如,有由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]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值