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

这里我们按照树形 DP 的一贯思路,设 \(dp_{i, j}\) 为 \(i\) 作为当前根节点,还能选 \(j\) 个节点的最优选择。
然后我们就可以推出这样的方程(设 \(l\) 为当前还能选择 \(l\) 节课):
(\(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;
}
浙公网安备 33010602011771号