题意:
给出的一棵树,树上每个节点都都有一个值,现在可以每次选择树上的一块联通区域,要求这个区域中必须包含节点1,每次可以选择的合法区域进行所有节点值+1或-1的操作,问要使所有节点的值都变为0,最少需要几步操作?
思路:
一道树形DP。不妨以节点1为根,这样可以发现,要让每个节点的值都为0,而且每一步的操作都要求包含根,那么儿子节点一定不会晚于父亲节点变成0,所以我们可以从下到上进行dp,每次先求出子树所需要的代价,然后根据子树的情况dp出父亲的。
这里需要的注意的是,dp不能仅仅保存子树的操作数,这样显然无法进行转移,其实,对于一个节点来说所需要的操作步数,就是increase操作和decrease操作的和,那么我们可以设dp[u][0]表示increase操作的步数,dp[u][1]表示decrease操作的步数,这样就可以进行转移了,对于每个节点,我们只要求出在所有的儿子节点的dp,就可以推算出使它变成0最少需要增加多少,减少多少,即可完成状态转移。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 10;
vector <int> tree[MAXN];
ll dp[MAXN][2], a[MAXN];
void dfs(int u, int pre) {
int cnt = tree[u].size();
ll inmax = 0, demax = 0;
for (int i = 0; i < cnt; i++) {
int v = tree[u][i];
if (v == pre) continue;
dfs(v, u);
inmax = max(inmax, dp[v][0]);
demax = max(demax, dp[v][1]);
}
ll tmp = inmax - demax;
if (a[u] + tmp >= 0) { dp[u][0] = inmax; dp[u][1] = a[u] + tmp + demax; }
else { dp[u][0] = inmax - (a[u] + tmp); dp[u][1] = demax; }
}
int main() {
//freopen("in.txt", "r", stdin);
int n;
scanf("%d", &n);
for (int i = 0; i < n - 1; i++) {
int u, v;
scanf("%d%d", &u, &v);
tree[u].push_back(v);
tree[v].push_back(u);
}
for (int i = 1; i <= n; i++)
scanf("%I64d", &a[i]);
dfs(1, -1);
printf("%I64d\n", dp[1][0] + dp[1][1]);
return 0;
}