树形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 \n",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\n",x,dp[x]);
    dp[x]=max(dp[x],n-num[x]);
    printf("结束 dp[%d]=%d num[%d]=%d \n",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\n",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\n",w);
    return 0;
}
牛客OI14普及C

 

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

posted @ 2020-03-10 15:50  守林鸟  阅读(489)  评论(0编辑  收藏  举报