题目:https://www.luogu.org/problemnew/show/P2014
首先,根据题意我们能很清楚的想象出一棵树。(为没有先修课的所有课加上一个虚拟的父亲节点)
一共可以选m门课,我们需要在树中选m个节点,使得所有节点上的权值(学分)加起来最大,注意每个节点可以背选当且仅当其父节点背选。
这是背包类树形dp的入门题。
其实很容易意识到这应该跟动态规划有关,因为节点有限制(必须选父节点),所以最优解不一定会包括权值最大的那单个节点。
对于树本题,我们设 dp[i][j] 。
i: 节点序号
j:在以i节点为根节点的树,选j门课
dp[i][j]:在以i节点为根节点的树,选j门课能够获得的最多学分
本题与一维背包问题有共同之处
看图:
dp[fat][m]是其各自子树最优解之和,再加上自身的权值
因为有一个m的限制,所有子树选了的课加起来不能超过m,因此我们可以将同一个子树看成不同的物品
例如son1 就可以看成 选了1节课的,选了2节课的,选了3节课的,同理将son2,son3都看成不同的物品,这是不是有点类似于一维背包了。背包大小就是m,选了k节课,那么k就是货物的重量,k加在一起不能超过m,求最大总权值。
但是还有一点就是一棵子树拆开来的不同物品不能同时选,比如son1拆开的那3个就只能选1个。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 307;
vector<int> cls[maxn];
int val[maxn],dp[maxn][maxn];
int n, m;
void solve(int num) {
dp[num][0] = 0;
for (int i = 0; i < cls[num].size(); i++) {
int &son = cls[num][i];
solve(son);
for (int j = m; j >= 0; j--) { //一维背包 j:fat k: son
for (int k = j; k >= 0; k--) {
dp[num][j] = max(dp[num][j], dp[num][j - k] + dp[son][k]);
}
}
}
if (num != 0) for (int i = m; i > 0; i--) dp[num][i] = dp[num][i - 1] + val[num]; //加上自己
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int fat, sco;
cin >> fat >> sco;
cls[fat].push_back(i);
val[i] = sco;
}
solve(0);
cout << dp[0][m] << endl;
return 0;
}