2925. 在树上执行操作以后得到的最大分数 | 树形dp

36 阅读2分钟

题目描述:

有一棵 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,如果保留根节点的值,则树一定是健康的,此时保留分数 v1=values[x]v1=values[x]; 如果不保留根节点的值,则要保证左右子树(如果存在)均为健康的,此时要保留的分数 v2=dfs(x>left)+dfs(x>right)v2=dfs(x->left)+dfs(x->right)。因此只需选择以上两种情况的最小值 dfs(x)=min(v1,v2)dfs(x)=min(v1,v2) 即可。

考虑先将所有的 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.