树形dp——树的重心(2) 代码调试理解

和树的最大独立问题类似,先任选一个结点作为根节点,把无根树变成有根树,然后设d(i)表示以i为根的子树的结点的个数。不难发现d(i)=∑d(j)+1,j∈s(i)。s(i)为i结点的所有儿子结点的编号的集合。程序也十分简单:只需要DFS一次,在无根树有根数的同时计算即可,连记忆化都不需要——因为本来就没有重复计算。
那么,删除结点i后,最大的连通块有多少个呢?结点i的子树中最大有max{d(j)}个结点,i的“上方子树”中有n-d(i)个结点,

代码调试帮助理解:

scanf:

6

1 2
2 3
2 4
4 5

5 6

printf:2 4

/**********
代码调试
**********/
#include <cstdio>  
#include <cmath>  
#include <cctype>  
#include <algorithm>  
#include <cstring>  
#include <utility>  
#include <string>  
#include <iostream>  
#include <map>  
#include <set>  
#include <vector>  
#include <queue>  
#include <stack>  
using namespace std;  
#define eps 1e-10  
#define inf 0x3f3f3f3f    
#define ll long long  
#define CL(a,b) memset(a,b,sizeof(a))  
#define MAXN 100010  
typedef long long LL;  
const int INF = 0x3f3f3f3f;  
const int maxn = 5e4 + 5;  
vector<int>G[maxn];  
int ans, n;
int dp[maxn],tot,num[maxn];

void init(){
    ans=INF;
    CL(dp,0);
}
void dfs(int u, int fa) {  //递归转化以u为根的子树,u的父亲是fa,统计各节点 子节点数目
    dp[u]=1;
    for(int i = 0; i < G[u].size(); ++i) {  
        int v = G[u][i];
        cout<<"v="<<v<<"  ";
        if(v != fa) {
            cout<<"dfs"<<v<<","<<u<<"  ";
            dfs(v, u);  //递归转化以v为根的子树
            dp[u]+=dp[v];
        }
    }  
}

void dp_dfs(int son,int fa){
    int maxx=0;
    for(int i = 0; i < G[son].size(); ++i) {
        int v = G[son][i];
        if(v != fa) {
            //cout<<"dfs"<<v<<","<<son<<"  ";
             dp_dfs(v, son); 
             maxx=max(maxx,dp[v]);
            //cout<<"maxx="<<maxx<<"  ";
        }
    }
    //更新答案  
    maxx = max(n-dp[son], maxx);//n-dp[son]==“上方子树”  maxx理解为“自己的子树”
    //cout<<"n-dp"<<son<<"="<<n-dp[son]<<"  ";
    //cout<<"newmaxx="<<maxx<<"  ";
    if(maxx < ans)  
    {  
        tot = 1;  
        ans = maxx;  
        num[tot] = son;  
    }  
    else if(maxx == ans)  
    {  
        tot++;  
        num[tot] = son;  
    }  
    
}
int main()  
{  
    int u,v;  
    while(scanf("%d",&n)==1)  
    {  
        init();for(int i=1;i<n;i++) G[i].clear();
        for(int i=1; i<n; i++){
            scanf("%d%d",&u,&v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        dfs(1, 0);  
        for(int i=1;i<=n;i++){
            cout<<"dp"<<i<<"="<<dp[i]<<" ** ";
        };
        //cout<<endl;
        dp_dfs(1, 0);  
        sort(num+1, num+tot+1);  
        printf ("%d",num[1]);  
        for(int i=2; i<=tot; i++)  
            printf (" %d",num[i]);  
        printf ("\n");  
    }  
    return 0;  
}  

代码调试:

更好的代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <vector>
#include <iostream>
 
using namespace std;
 
int N; // 1<= N <= 20000
const int maxn = 20000;
vector<int> tree[maxn + 5]; // tree[i]表示节点i的相邻节点
int d[maxn + 5]; // d[i]表示以i为根的子树的节点个数
 
#define INF 10000000
 
int minNode;
int minBalance;
 
void dfs(int node, int parent) // node and its parent
{
    d[node] = 1; // the node itself
    int maxSubTree = 0; // subtree that has the most number of nodes
    for (int i = 0; i < tree[node].size(); i++) {
        int son = tree[node][i];
        if (son != parent) {
            dfs(son, node);
            d[node] += d[son];
            maxSubTree = max(maxSubTree, d[son]);
        }
    }
    maxSubTree = max(maxSubTree, N - d[node]); // "upside substree with (N - d[node]) nodes"
 
    if (maxSubTree < minBalance){
        minBalance = maxSubTree;
        minNode = node;
    }
}
 
int main()
{
    int t;
    scanf("%d", &t);
    while (t--){
        scanf("%d", &N);
 
        for (int i = 1; i <= N - 1; i++){
            tree[i].clear();
        }
 
        for (int i = 1; i <= N-1; i++){
            int u, v;
            scanf("%d%d", &u, &v);
            tree[u].push_back(v);
            tree[v].push_back(u);
        }
 
        minNode = 0;
        minBalance = INF;
 
        dfs(1, 0); // fist node as root
 
        printf("%d %d\n", minNode, minBalance);
    }
 
    return 0;
}

参考资料:百度百科https://baike.baidu.com/item/树的重心/20416316


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值