题目描述
难度分:1347
输入n(1≤n≤105)。
输入一棵无向树的n−1条边,边权为1,节点编号从1到n。
输入长为n的数组a(1≤a[i]≤109)。
定义f(x)=Σni=1dist(x,i)×a[i],其中dist(x,i)表示x到i的最短路长度。
输出f(1),f(2),……,f(n)中的最小值。
输入样例1
4
1 2
1 3
2 4
1 1 1 2
输出样例1
5
输入样例2
2
2 1
1 1000000000
输出样例2
1
输入样例3
7
7 3
2 5
2 4
3 1
3 6
2 1
2 7 6 9 3 4 6
输出样例3
56
算法
换根DP
非常明显的一个换根DP
,定义两个数组w和f,w[u]表示以u为根节点的子树所有节点的a[i]之和。
动态规划
状态定义
f[u]表示以u为根的子树中,所有子节点到u的f值。这样一来,就可以先随便定一个根,比如1,从1开始进行DFS
计算出一个初始的f数组和w数组。
状态转移
假设v是u的子节点,那么u子数中所有子孙节点到u的f值为f[u]=Σvf[v]+w[v]。含义是“子树v中的所有节点先到v,即f[v]。再从v向上走一步到达u,加上子树v中所有节点的权值w[v]$”。
DFS
换根
从1开始,换根计算以u为整个树的根时,f值会如何变化。定义递归函数dfs(u,vx),u是当前节点,vx表示u上面所有节点到u的f值。那么最终答案就应该minnu=1(f[u]+vxu)。
还是遍历u的所有子节点v,当以v为整棵树的根时,我们需要计算v的vx。一部分是u上面所有节点的贡献vx,另一部分是f[u]中除去v子树的贡献f[u]−f[v]−w[v](即v的兄弟子树贡献)。这些节点到u的f值就是f[u]−f[v]−w[v]+vx,它们到达u之后还需要算上节点u往v再走一步,所以还需要加上Σni=1a[i]−w[v]=w[1]−w[v],也就说明vxv=f[u]−f[v]−w[v]+vxu+w[1]−w[v]。
这时候我们只需要DFS
遍历整棵树,维护f[u]+vxu的最小值就可以了。
复杂度分析
时间复杂度
换根DP
本质上就是对整棵树DFS
遍历两遍,时间复杂度为O(n)。
空间复杂度
子树的权值和数组w空间消耗是线性的;子树中所有节点到根节点的f值数组f空间消耗也是线性的;树的邻接表空间复杂度还是线性的。除此之外,在算法过程中DFS
在整棵树退化为链的情况下,最大递归深度为O(n)。因此,整个算法的额外空间复杂度为O(n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010;
int n, a[N];
vector<int> graph[N];
LL f[N], w[N], ans;
void dfs1(int u, int fa) {
w[u] = a[u];
for(int v: graph[u]) {
if(v == fa) continue;
dfs1(v, u);
w[u] += w[v];
f[u] += f[v] + w[v];
}
}
void dfs2(int u, int fa, LL vx) {
ans = min(ans, f[u] + vx);
for(int v: graph[u]) {
if(v == fa) continue;
dfs2(v, u, vx + f[u] - f[v] - w[v] + w[1] - w[v]);
}
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
graph[i].clear();
f[i] = w[i] = 0;
}
for(int i = 1; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
graph[u].push_back(v);
graph[v].push_back(u);
}
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
dfs1(1, 0);
ans = 9e18;
dfs2(1, 0, 0);
printf("%lld\n", ans);
return 0;
}