zoukankan      html  css  js  c++  java
  • 树形dp求解树的重心和例题

    树的重心:https://oi-wiki.org/graph/tree-centroid/

    定义

    对于树上的每一个点,计算其所有子树中最大的子树节点数,这个值最小的点就是这棵树的重心。(最大的子树最小)

    性质

    1. 以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。
    2. 树中所有 点到某个点的距离和 中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。
    3. 把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树的重心的路径上。
    4. 在一棵树上添加或删除一个叶子,那么它的重心最多只移动一条边的距离。

    求法

    树的算法几乎都是建立在dfs上,遍历整棵树的过程中记录 所有点 作为根节点中 所有子树的节点数,树形dp,再遍历dp数组。

     

    例题1:POJ1655

    题意:n个点的树找重心,如果有两个,输出编号小的。

    思路:以任意一点(一般直接选1)为根进行dfs,在遍历过程中,其中有2个变量,num[x]表示以x为根的子树的节点数量(包含自身),dp[x]表示若是以x为重心删除,拆出来的子树 的最大节点数量是多少?根据重心定义,最大的子树最小。

    #include<stdio.h>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<math.h>
    #include<string>
    #include<map>
    #include<queue>
    #include<stack>
    #include<set>
    #include<ctime>
    #define ll long long
    #define inf 0x3f3f3f3f
    const double pi=3.1415926;
    using namespace std;
    
    vector<int>a[20005];
    int vis[20005];
    int num[20005];
    int dp[20005];
    int t,n,k;
    
    
    void dfs(int x,int last)///当前点,父节点
    {
    
        dp[x]=0;num[x]=1;///num[x]表示以自己为根节点的树有多少个节点,自己算一个,后续累加子树带来的贡献。
        printf("开始 dp[%d]=%d num[%d]=%d 
    ",x,dp[x],x,num[x]);
        int res=1;///自己是一个节点,看看有没有没有剪掉的子节点贡献给自己顺便返回给父亲
        for(int i=0;i<a[x].size();i++)
        {
            int next=a[x][i];
            if(next==last)
                continue;
            dfs(next,x);
            dp[x]=max(dp[x],num[next]);
            num[x]+=num[next];
        }
        //printf("dp[%d]=%d
    ",x,dp[x]);
        dp[x]=max(dp[x],n-num[x]);
        printf("结束 dp[%d]=%d num[%d]=%d 
    ",x,dp[x],x,num[x]);
    }
    
    int main()
    {
        scanf("%d",&t);
        while(t--)
        {
            scanf("%d",&n);
            for(int i=1;i<=n;i++)
            {
                a[i].clear();
                vis[i]=0;
                num[i]=0;
            }
    
            for(int i=1;i<n;i++)
            {
                int x,y;
                scanf("%d%d",&x,&y);
                a[x].push_back(y);
                a[y].push_back(x);
            }
            dfs(1,-1);
            int idx=1,cnt=dp[1];
            for(int i=2;i<=n;i++)
            {
                if(dp[i]<cnt)
                    idx=i,cnt=dp[i];
            }
            printf("%d %d
    ",idx,cnt);
        }
        return 0;
    }
    POJ1655

    例题2:https://ac.nowcoder.com/acm/contest/4479/C

    题意:n个点的树,把某一个点作为根节点root,根节点的深度deeproot为0,其他节点到这个根节点的深度为deepi,求所有点的深度和最小是多少。

    思路:根据第2条性质,树形dp找重心,再来一次dfs计算深度和。

    #include<stdio.h>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<math.h>
    #include<string>
    #include<map>
    #include<queue>
    #include<stack>
    #include<set>
    #include<ctime>
    #define ll long long
    #define inf 0x3f3f3f3f
    const double pi=3.1415926;
    using namespace std;
    
    vector<int>a[1000005];
    int n,w=0;;
    int vis[1000005];
    int dp[1000005];
    int num[1000005];
    
    void find(int x,int last)///当前点,父节点
    {
    
        dp[x]=0;num[x]=1;///num[x]表示以自己为根节点的树有多少个节点,自己算一个,后续累加子树带来的贡献。
        int res=1;///自己是一个节点,看看有没有没有剪掉的子节点贡献给自己顺便返回给父亲
        for(int i=0;i<a[x].size();i++)
        {
            int next=a[x][i];
            if(next==last)
                continue;
    
            find(next,x);
            dp[x]=max(dp[x],num[next]);
            num[x]+=num[next];
        }
        dp[x]=max(dp[x],n-num[x]);
    }
    
    void dfs(int now,int deep){///计算深度和
        vis[now]=1;
        w=w+deep;
        for(int i=0;i<a[now].size();i++){
            if(vis[  a[now][i] ]==0){
                dfs(a[now][i],deep+1);
            }
        }
    }
    
    
    int main(){
        scanf("%d",&n);
        for(int i=1;i<n;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            a[x].push_back(y);
            a[y].push_back(x);
        }
        find(1,-1);
        int idx=1,cnt=dp[1];
        for(int i=2;i<=n;i++){///找重心
            if(dp[i]<cnt)
                idx=i,cnt=dp[i];
        }
        dfs(idx,0);
        printf("%d
    ",w);
        return 0;
    }
    牛客OI14普及C

    天上不会掉馅饼,努力奋斗才能梦想成真!

  • 相关阅读:
    CentOS ping www.baidu.com 报错 name or service not know
    python2 当中 遇到 UnicodeDecodeError UnicodeDecodeError: 'ascii' codec can't decode byte 0xe9 in position 37: ordinal not in range(128)
    shell基本语法
    android---笔记 AppContext extends Application
    android笔记--加载框
    android笔记--与服务器交互更改简历状态
    android笔记---AndroidManifest.xml
    android笔记-----消息提示
    ios笔记一 追加数据
    android笔记一 控件属性
  • 原文地址:https://www.cnblogs.com/shoulinniao/p/12455654.html
Copyright © 2011-2022 走看看