树形dp的题目,yxc说这是最经典的树形dp题,所以想把这题弄明白些。
原本不想写题解的,因为难度确实不大,但是阳了之后逻辑能力被严重耽误,很那思考多一些的东西,感觉大脑的“内存”不够了,遂借助文章的形式梳理一遍题目,顺便写下来充实博客。
核心是以下两个状态转移方程,等下解释这两个转移方程的内含。
d
p
[
i
]
[
0
]
=
∑
m
a
x
(
d
p
[
s
o
n
]
[
0
]
,
d
p
[
s
o
n
]
[
1
]
)
dp[i][0] = \sum max(dp[son][0],dp[son][1])
dp[i][0]=∑max(dp[son][0],dp[son][1])
d
p
[
i
]
[
1
]
=
∑
d
p
[
s
o
n
]
[
0
]
+
h
a
p
p
y
[
i
]
dp[i][1] = \sum dp[son][0]+happy[i]
dp[i][1]=∑dp[son][0]+happy[i]
为什么说是树形的dp呢?因为树上每个结点都是可以一个状态的集合,当做状态来去转移,也就可使用动态规划了。
思路:根据题目,观察可知,父结点的状态,可以由子结点转移过来,这样一来,就可以使用dfs的方式遍历整个树,然后进行比较root结点两个状态中那个最大即可。
如何转移:每个结点有两种状态,取值或者不取;
当结点i取值时,其最大值为所有子节点不取值的和以及i结点自己的值;
当结点i不取值时,其子节点们就可以取值了,这样一来,i结点的最大值为其所有子节点最大值的和(取值或者不取值~);
根据以上思路,可得转移方程:
d
p
[
i
]
[
0
]
=
∑
m
a
x
(
d
p
[
s
o
n
]
[
0
]
,
d
p
[
s
o
n
]
[
1
]
)
dp[i][0] = \sum max(dp[son][0],dp[son][1])
dp[i][0]=∑max(dp[son][0],dp[son][1])
d
p
[
i
]
[
1
]
=
∑
d
p
[
s
o
n
]
[
0
]
+
h
a
p
p
y
[
i
]
dp[i][1] = \sum dp[son][0]+happy[i]
dp[i][1]=∑dp[son][0]+happy[i]
因此,只要从底层遍历数组即可,而dfs正好满足从底层遍历的要求,因此可以直接使用dfs遍历树的同时进行计算。
这个dp是从最底层开始走的,如同从图中7,8开始。底层策略决定了上层的决策,这何尝不是一种现实体验呢。
import java.util.*;
/**
* @desc 使用acwing(oj)用的模板
*/
public class Main {
//定义容器、常数、读入
int N = 6010;
HashMap<Integer,List<Integer>> tree = new HashMap<>();
int[][] f = new int[N][N];
int n;
boolean[] findRoot;
Scanner jin = new Scanner(System.in);
//oj要用的main方法
public static void main(String[] args) {new Main().run();}//在oj中调用题解方法
void run() {
//读入题目数据
n = jin.nextInt();
for(int i = 1; i <= n; i++){
f[i][1] = jin.nextInt();//读入数据+初始化f
}
findRoot = new boolean[n+1];
//构造树
for(int i = 0; i < n-1; i++){
int son = jin.nextInt();
int father = jin.nextInt();
findRoot[son] = true;
if(tree.get(father) == null) tree.put(father,new ArrayList<>());
tree.get(father).add(son);
}
//调用解题方法
int root = -1;
for(int i = 1; i <= n; i++){
if(!findRoot[i]){
root = i;
break;
}
}
dfs(root);
int res = Math.max(f[root][1],f[root][0]);
//输出题解答案
System.out.println(res);
}
void dfs(int root) {
List<Integer> sons = tree.get(root);
if(sons == null) return;
for(int son : sons){
dfs(son);
f[root][1]+=f[son][0];
f[root][0]+=Math.max(f[son][1],f[son][0]);//只有循环结束后,f[root][0]才更新完毕
}
}
}