题目描述
设有一棵二叉树,如图:
其中,圈中的数字表示结点中居民的人口。圈边上数字表示结点编号,现在要求在某个结点上建立一个医院,使所有居民所走的路程之和为最小,同时约定,相邻接点之间的距离为 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;
}