【C++】社区医院建设(树形DP/二次扫描与换根法)

题目描述

设有一棵二叉树,如图:

其中,圈中的数字表示结点中居民的人口。圈边上数字表示结点编号,现在要求在某个结点上建立一个医院,使所有居民所走的路程之和为最小,同时约定,相邻接点之间的距离为 1。如上图中,若医院建在1 处,则距离和 =4+12+2×20+2×40=136;若医院建在 3 处,则距离和 =4×2+13+20+40=81。

输入格式

第一行一个整数 n,表示树的结点数。

接下来的 n 行每行描述了一个结点的状况,包含三个整数 w u v,其中 w 为居民人口数,u 为左链接(为 0 表示无链接),v 为右链接(为 0 表示无链接)。

输出格式

一个整数,表示最小距离和。

输入输出样例

输入 

5						
13 2 3
4 0 0
12 4 5
20 0 0
40 0 0

输出 

81

思路

朴素做法:n只有1e2,枚举每个节点dfs,复杂度为O(n^2)。

优化做法:对于这种没有明显树根的题,可以采用二次扫描换根,先dfs预处理,然后枚举每个点进行答案的更新。

具体就是:dfs的时候要预处理size[i],即当以1这个节点为树根时(换根),以i节点为根的子树的节点数。这里的节点数实际上是子树的节点的权值和,和普通的树的重心不一样。同时,dfs时还能求出以1为树根时的距离和,存到f[1]里。

然后从1开始进行转移,设x的下一个节点是y,相较于x作为树根的路径和f[x],可以看到f[y]的值的变化情况:
1.减少了size[y]*1:以y为根的子树的所有节点原来要走到x,现在可以少走一条边,总的就是减少了size[y].
2.增加了(size[1]-size[y])*1:除去以y为根的子树的节点,还剩下size[1]-size[y]个节点(乘上权值),而这些节点需要多走一步。

转移方程就可以写出来了f[y]=f[x]+(size[1]-size[y])*1-size[y]*1; 满足无后效性,而且能知道dp的就是以1为树根的树的当前深度。对答案取min更新即可。

AC_Code

#include <bits/stdc++.h>
#include <vector>
#include <map>
#include <stack>
#define ll long long 
using namespace std;
const int N = 110; 
int n, m, t, ans;
int he[N], v[2*N], Ne[2*N];
int num[N], f[N], size[N];

void add(int x,int y) {v[++t]=y, Ne[t]=he[x], he[x]=t;}

void getdfs(int x, int d, int pre) {
    int i;
    size[x] = num[x];
    for(i=he[x]; i; i=Ne[i]) {
        int y = v[i];
        if(y==pre)  continue;
        getdfs(y, d+1, x);
        size[x] += size[y];
    }
    f[1] += num[x]*(d-1);
}

void getdp(int x,int pre) {
    int i;
    for(i=he[x]; i; i=Ne[i]) {
        int y = v[i];
        if(y==pre)  continue;
        f[y] = f[x]+(size[1]-size[y])*1-size[y]*1; 
        getdp(y,x);
    }
    ans = min(ans,f[x]);
}

int main()
{
    cin>>n;
    int i;
    for(i=1;i<=n;i++) {
        int w,u,v;    
		cin>>w>>u>>v;
        num[i]=w;
        if(u)  add(i,u),add(u,i);
        if(v)  add(i,v),add(v,i);
    }
    getdfs(1,1,0);
    ans = f[1];
    getdp(1,0);
    cout << ans;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Luoxiaobaia/article/details/124712950
今日推荐