传送门 | 难度 |
---|---|
https://www.luogu.com.cn/problem/P2014 | 提高+/省选- |
该题是一道经典的树形DP题目,基本就是树形DP的板子题。
分析中符号说明
- dp[][]:动态规划数组dp[i][j]表示i为根结点还可选j门课的情况下的最大值
- x:父结点
- y:子结点
- v[]:价值数组
分析
- 森林=>树
输入的是一个森林,不太容易套树形DP,所以增加一个选修课0,把森林转换成树,同时要把选择数量变成M+1 - 状态转移方程
依据是否把子树放到背包中,再考虑使用子树需要的边,可以得到
dp[x][j] = max{ dp[x][j], dp[x][j - k] + dp[y][k] }
套板子,j的上限为M + 1 - 1 = M
,所以j的范围是:0 ≤ j ≤ M
,因为是0/1背包所以采用倒序,
k的范围是:0 ≤ k ≤ j
,遍历顺序无所谓 - 注意:板子在dfs的最后是有一个倒序的背包的,就是为了把j上限留出的坑(j上限是M而不是M+1,留出了1的坑)补上去。
注意点的解释参考:https://www.luogu.com.cn/blog/wjyyy/solution-p2014
为什么最后两行要单独拿出来做呢?
for(int i=s[x];i>=0;i–)
f[x][i+1]=f[x][i]+p[x];
我们回到题面上,父亲是儿子的先修课,所以没有父亲时,儿子再多也没有用,背包中处理的子树是不带根结点的,我们要加上,否则会出现下面这种状况:否则
如果在做的过程中将根结点算入子树,那么 f[1][2]最终值将会是 11 (在正确的过程中也是 11 ,但是会被更新到 f[1][3]),不做更新或再去做一遍更新就不对了,
AC代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 305, MAXM = 305;
int to[MAXN], h[MAXN], v[MAXN], ne[MAXN], idx;
int dp[MAXN][MAXM];
int M, N;
int si, ki;
void addEdge(int be, int en) {
to[++idx] = en; ne[idx] = h[be]; h[be] = idx;
}
void dfs_dp(int x) {
for (int i = h[x]; i != -1; i = ne[i]) {
int y = to[i];
dfs_dp(y);
for (int j = M + 1 - 1; j >= 0; --j) {
for (int k = 0; k <= j; ++k) {
dp[x][j] = max(dp[x][j], dp[x][j - k] + dp[y][k]);
}
}
}
for (int i = M + 1; i >= 1; --i)//填坑
dp[x][i] = dp[x][i - 1] + v[x];
}
int main() {
memset(h, -1, sizeof(h));
scanf("%d%d", &N, &M);
for (int i = 1; i <= N; ++i) {
scanf("%d%d", &si, &ki);
addEdge(si, i);
v[i] = ki;
}
dfs_dp(0);
printf("%d\n", dp[0][M + 1]);
return 0;
}