题目描述:
有一棵
n
个节点的无向树,节点编号为0
到n - 1
,根节点编号为0
。给你一个长度为n - 1
的二维整数数组edges
表示这棵树,其中edges[i] = [ai, bi]
表示树中节点ai
和bi
有一条边。同时给你一个长度为
n
下标从 0 开始的整数数组values
,其中values[i]
表示第i
个节点的值。一开始你的分数为
0
,每次操作中,你将执行:
- 选择节点
i
。- 将
values[i]
加入你的分数。- 将
values[i]
变为0
。如果从根节点出发,到任意叶子节点经过的路径上的节点值之和都不等于 0 ,那么我们称这棵树是 健康的 。
你可以对这棵树执行任意次操作,但要求执行完所有操作以后树是 健康的 ,请你返回你可以获得的 最大分数 。
本题是一道树形dp的题目,先弄清问题是什么,再找子树条件。分析题目可知要在保证树是健康的前提下,尽可能少得保留树上的分数。从最简单开始思考,对于给定的树 x
,如果保留根节点的值,则树一定是健康的,此时保留分数 ; 如果不保留根节点的值,则要保证左右子树(如果存在)均为健康的,此时要保留的分数 。因此只需选择以上两种情况的最小值 即可。
考虑先将所有的 values
加入到答案中(即将所有节点的值置 0
),然后考虑以节点 x
为根的树至少需要保留多少分数来保证健康,并从答案中删除这个分数。
class Solution {
public long maximumScoreAfterOperations(int[][] edges, int[] values) {
List<Integer>[] g = new ArrayList[values.length];
// 初始化一个由List组成的数组
Arrays.setAll(g, e -> new ArrayList<>());
// 为根节点添加一个父节点,方便书写
g[0].add(-1);
for(int[] e : edges) {
int x = e[0], y = e[1];
g[x].add(y);
g[y].add(x);
}
long ans = 0;
for(int v : values) ans += v;
return ans - dfs(0, -1, g, values);
}
// 保证以x为根的子树为健康树时,树上的最小分数
// 传入父节点fa,用以区分子树
private long dfs(int x, int fa, List<Integer>[] g, int[] values) {
// 对于叶子节点,直接返回节点值
if(g[x].size() == 1) return values[x];
long loss = 0;
// 不保留根的分数,因此要保证每个子树为健康的
for(int y : g[x]) {
// 以y为根的子树健康时的最小分数
if(y != fa) {
loss += dfs(y, x, g, values);
}
}
return Math.min(loss, values[x]);
}
}
keywords —— java语法; 正难则反; dfs; 树形dp.