在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 NNN 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程 a 是课程 b 的先修课即只有学完了课程 a,才能学习课程 b)。一个学生要从这些课程里选择 MMM 门课程学习,问他能获得的最大学分是多少?
第一行有两个整数 NNN , MMM 用空格隔开。( 1≤N≤3001 \leq N \leq 3001≤N≤300 , 1≤M≤3001 \leq M \leq 3001≤M≤300 )
接下来的 NNN 行,第 I+1I+1I+1 行包含两个整数 $k_i $和 sis_isi, kik_iki 表示第I门课的直接先修课,sis_isi 表示第I门课的学分。若 ki=0k_i=0ki=0 表示没有直接先修课(1≤ki≤N1 \leq {k_i} \leq N1≤ki≤N , 1≤si≤201 \leq {s_i} \leq 201≤si≤20)。
只有一行,选 MMM 门课程的最大得分。
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
13
这道题是一道很典型的树形背包DP问题的模板题目。这道题唯一需要注意的是,由于每个课都有一个先选课。所以最终这些课程之间的关系会构成一个森林,但是我们的树形DP是在一棵树上进行的,所以我们需要将这些割裂的子树用一个节点统一起来,我们把这个这节点编号为000,这个根叫做超根。
那么这个超根我们是要选择的,只不过它对答案的贡献是0。也就是说我们需要选择的是M+1M + 1M+1个节点。
如果不懂树形背包DP的话,可以看作者之前的文章,讲解很详细:AcWing 10. 有依赖的背包问题(分组背包问题 + 树形DP)
这里写的是空间优化后的代码。
#include
#define endl '\n'
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair pii;
const int N = 300+ 10;
vectoredge[N];
int f[N][N];
int score[N];
int n, m;void dp(int u, int M)
{f[u][1] = score[u];for(int i = 0; i < edge[u].size(); i ++ ){int son = edge[u][i];dp(son, M - 1);for(int j = M; j >= 0; j -- ){for(int q = 0; q <= j - 1; q ++ ){f[u][j] = max(f[u][j - q] + f[son][q], f[u][j]);}}}
}void solve()
{cin >> n >> m;for(int i = 0; i < n; i ++ ){int x, y;cin >> x >> y;score[i + 1] = y;edge[x].push_back(i + 1);}dp(0, m + 1);cout << f[0][m + 1] << endl;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);solve();
}