P2014 [CTSC1997] 选课 题解

P2014 [CTSC1997] 选课 题解

这道题目用的是树形 DP 算法。我在暑假里学的,现在忘得差不多了……写篇题解来复习一下(其实之前理解的也不是很好)。

简化题意

这题题意挺明白的,我就复述一遍吧。
\(n\) 种课程,第 \(i\) 节课有一个价值 \(s_i\) 与一个条件 \(k_i\),代表着要想学习课程 \(i\),必须先学课程 \(k_i\)。若 \(k_i = 0\),则学习这节课没有条件。
我们希望选 \(m\) 节课并最大化它们的价值之和。

解题思路

看到 \(n\) 节课里面选 \(m\) 节,每节课有自己的价值,下意识就想到跟背包 DP 有关。

但是这里因为每节课有自己的条件,就没法把它们分开来选,而是得看着条件进行 DP。这点就很烦了。
我们把每节课程视作一个节点,它的条件课程看做此节点的父节点,就可以把整个课程列表抽象成一个森林了。但是这样还是很麻烦,因为我们如果要一棵树一棵树的做,会很麻烦。
这里容易想到一个小技巧:就是\(0\) 看做根节点,那么整个森林就变成了一棵树,非常方便我们操作。

这里因为数据结构是,而我们又要进行背包 DP,且有先决条件,很容易想到要进行树形 DP

接下来要进行状态定义了。我在这里放一张图片,来自洛谷的第二篇题解。这部分内容写的很好,感谢。

image

这里我们按照树形 DP 的一贯思路,设 \(dp_{i, j}\)\(i\) 作为当前根节点,还能选 \(j\) 个节点的最优选择。
然后我们就可以推出这样的方程(设 \(l\) 为当前还能选择 \(l\) 节课):

\[dp_{i, j} = dp_{father(i), j} + s_i(0 \leq j \leq l - 1) \]

\[dp_{father(i), j} = \max(dp_{father(i), j}, dp_{i, j - 1}) \]

\(j - 1\) 的原因是选择了 \(i\) 节点)

这里再说明一下,第二个状态转移方程是在回溯过程中做的。这样是因为我们在 \(father(i)\) 节点的时候还不知道 \(i\) 节点的最优解,要把下面的所有最优解算完才能转移到上面的。这也是树形 DP 的一贯思想,需要牢记!

代码部分

#include <bits/stdc++.h>
using namespace std;
int dp[310][310], s[310];
vector<int> g[310];
int n, m;
void dfs(int u, int k)
{
	if(k <= 0)
		return;
	for(int i = 0;i < g[u].size();i ++)
	{
		int v = g[u][i];
		for(int j = 0;j < k;j ++)
			dp[v][j] = dp[u][j] + s[v];
		dfs(v, k - 1);
		for(int j = 1;j <= k;j ++)
			dp[u][j] = max(dp[u][j], dp[v][j - 1]);
	}
}
int main()
{
	cin >> n >> m;
	for(int i = 1, u;i <= n;i ++)
	{
		cin >> u >> s[i];
		g[u].push_back(i);
	}
	dfs(0, m);
	cout << dp[0][m] << endl;
	return 0;
}

posted on 2026-05-31 21:10  Leo_29  阅读(8)  评论(0)    收藏  举报

导航