问题描述
有一棵 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为结点权值,结点之间关系为下图:
再读题,如果一个结点被选择了,那么它的邻接点都不能被选择。
假设选择了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]));
}
}
```
```