洛谷P2014 选课(树形DP)

传送门 难度
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];
我们回到题面上,父亲是儿子的先修课,所以没有父亲时,儿子再多也没有用,背包中处理的子树是不带根结点的,我们要加上,否则会出现下面这种状况:否则洛谷P2014 选课(树形DP)_第1张图片
如果在做的过程中将根结点算入子树,那么 f[1][2]最终值将会是 11 (在正确的过程中也是 11 ,但是会被更新到 f[1][3]),不做更新或再去做一遍更新就不对了,

AC代码

#include
#include
#include
#include

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;
}

你可能感兴趣的:(DP—树形DP,DP—背包)