蓝桥杯 算法训练 ALGO-4 结点选择

149 阅读1分钟

问题描述

有一棵 n 个节点的树,树上每个节点都有一个正整数权值。如果一个点被选择了,那么在树上和它相邻的点都不能被选择。求选出的点的权值和最大是多少?

输入格式

第一行包含一个整数 n 。

接下来的一行包含 n 个正整数,第 i 个正整数代表点 i 的权值。

接下来一共 n-1 行,每行描述树上的一条边。

输出格式

输出一个整数,代表选出的点的权值和的最大值。

样例输入

5

1 2 3 4 5

1 2

1 3

2 4

2 5

样例输出

12

样例说明

选择3、4、5号点,权值和为 3+4+5 = 12 。

数据规模与约定

对于20%的数据, n <= 20。

对于50%的数据, n <= 1000。

对于100%的数据, n <= 100000。

权值均为不超过1000的正整数。

题解

读题,根据样例输入得出,1 2 3 4 5为结点权值,结点之间关系为下图:

Snipaste_2023-03-22_14-29-03.jpg

再读题,如果一个结点被选择了,那么它的邻接点都不能被选择。

假设选择了1,就不能选择相邻的2、3

假设选择了2,就不能选择相邻的1、4、5

分析,这是一道树形动态规划问题(深度优先搜索+动态规划)。

  • 题目给出的树不一定是二叉树,所以可以当做图来处理,使用邻接表存储结点之间的关系,深度优先遍历图
  • 从底向上,先计算“子结点”的最大权值(两种,选择和不选择的值),再根据子结点值“更新当前节点最大权值”,所以使用动态规划

每个结点的最大权值都有两种情况,选择和不选择。

对于 i 结点, j 是 i 的邻接点

  • 若不选择 i 结点,其相邻的结点 j 可以选、也可以不选,取最大,则dp[i]+= max(dp[j], dp[j]),结果为:子结点 j 选择和不选择的值,取最大,累加。
  • 若选择 i 结点,其相邻的结点 j“不可以选“,则dp[i] += dp[j],结果为:不选择子结点 j 的值累加。

dp[i]表示不选择 i 结点的结果;

dp[i]表示选择 i 结点的结果;

状态转移方程为:

  • dp[i] += max(dp[j], dp[j]);
  • dp[i] += dp[j];

累加到根结点,对根结点的两种情况(选择,不选择)取最大值,即为权值和最大。

注意:使用一个变量 far 保存当前结点(son)的父结点,如果son==far说明访问到父结点了,为了防止重复访问,在son!=far 时才继续深度优先遍历下去。

代码

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * @Auther: Ban
 * @Date: 2023/3/22 11:08
 * @Description:
 * 树形DP问题(深度优先搜索+动态规划)
 */
public class NodeSelection {
    // dp[i][0]表示不取i节点的结果
    // dp[i][1]表示取i节点的结果
    public static int[][] dp;
    // 邻接表,存储节点之间关系
    public static List<List<Integer>> g = new ArrayList<>();

    public static void dfs(int son, int far) {
        List<Integer> node = g.get(son);
        for (int i = 0; i < node.size(); i++) {
            int temp = node.get(i); // 得到 son 的邻接点
            if (temp != far) { // 邻接点不能是父节点,要得到子节点
                dfs(temp, son); //深度优先遍历
                dp[son][0] += Math.max(dp[temp][0], dp[temp][1]); // 不选择,子节点选择和不选择的值,取最大,累加。
                dp[son][1] += dp[temp][0]; // 选择,不选择子节点的值累加。
            }
        }
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        in.nextLine(); // 读取Enter,以防下一个nextLine读取到(会结束输入)
        String[] str = in.nextLine().split(" ");
        dp = new int[n][2];
        for (int i = 0; i < n; i++) {
            dp[i][1] = Integer.parseInt(str[i]); // 初始化,存放每个节点权值
            g.add(new ArrayList<Integer>()); // 节点
        }
        // 构建图,使用邻接表来存储节点关系
        for (int i = 0; i < n - 1; i++) {
            String[] ab = in.nextLine().split(" ");
            int a = Integer.parseInt(ab[0]) - 1;
            int b = Integer.parseInt(ab[1]) - 1;
            // 关系
            g.get(a).add(b);
            g.get(b).add(a);
        }
        in.close();
        dfs(0, -1);
        // 累加到根节点,对根节点的两种情况(选择,不选择)取最大值,即为权值和最大。
        System.out.println(Math.max(dp[0][0], dp[0][1]));
    }
}
```
```