算法训练 结点选择
时间限制:1.0s 内存限制:256.0MB
问题描述
有一棵 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的正整数。
思路
参考博客:传送门
树形动态规划:采用DFS+动态规划解题
由题意,采用领接表(记住head数组初始化)存储树的所有边,每个节点当做根节点再连接其他与之相连的节点,最后组合为一棵树。设置dp[ i ][ j ]表示第 i 个节点的最大权值(j=0表示不选,j=1表示选)
可得状态转移方程:
①dp[ i ][ 1 ] += dp[ j ][ 0 ]; 表示第 i 个节点选,则第j个节点( i 的相邻节点)不选
②dp[ i ][ 0 ] += max(dp[ j ][ 1 ], dp[ j ][ 0 ]); 表示第 i 个节点不选,那么第 j 个节点可选可不选,找最大值相加
DFS搜索直到叶节点,然后依次往上回溯计算最大权值。
注意:因为是给的无向的边,所以可能出现某节点的下一个节点是其父节点,遇到此节点跳过即可。
该题JAVA超时(过50%),可采用同样的思路用C++等语言通过。
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static final int N = 100005;
static int cnt = 0;
static int[][] dp = new int[N][2];
static Node[] node = new Node[N*2];
static int[] head = new int[N];
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
Arrays.fill(head, 0, head.length, -1);
int n = cin.nextInt();
for(int i=1; i<=n; i++) {
dp[i][1] = cin.nextInt(); //第i个节点被选
}
for(int i=1; i<n; i++) {
int u = cin.nextInt();
int v = cin.nextInt();
Add(u, v);
Add(v, u);
}
Dfs(1, -1);
int ans = Math.max(dp[1][0], dp[1][1]);
System.out.println(ans);
cin.close();
}
private static void Dfs(int u, int pre) {
for(int i=head[u]; i!=-1; i=node[i].nex) {
int v = node[i].to;
if(v == pre) continue;
Dfs(v, u);
dp[u][1] += dp[v][0]; //选根节点
dp[u][0] += Math.max(dp[v][0], dp[v][1]); //不选
}
}
private static void Add(int v, int u) {
node[cnt] = new Node();
node[cnt].to = u; //边的终点
node[cnt].nex = head[v]; //指向与v相连的另(/上)一条边的编号
head[v] = cnt++; //记录v点在领接表的纵向位置
}
static class Node{
int to;
int nex;
}
}
如有错误或不合理的地方,敬请指正~
加油!!