洛谷 P2014 选课
Description
- 在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有N门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程a是课程b的先修课即只有学完了课程a,才能学习课程b)。一个学生要从这些课程里选择M门课程学习,问他能获得的最大学分是多少?
Input
-
第一行有两个整数N,M用空格隔开。(1<=N<=300,1<=M<=300)
接下来的N行,第I+1行包含两个整数ki和si, ki表示第I门课的直接先修课,si表示第I门课的学分。若ki=0表示没有直接先修课(1<=ki<=N, 1<=si<=20)。
Output
- 只有一行,选M门课程的最大得分。
Sample Input
7 4 2 2 0 1 0 4 2 1 7 1 7 6 2 2
Sample Output
13
题解:
- 树形dp。
- 首先他可能是个森林,所以给它来一个虚点0。观察发现,这是一个有依赖的树形dp。那么我们就按照背包的思路在树上进行dp。dp(i, j)表示在以i为根节点的子树中选j门课的最大价值。转移比较容易,第一层(i)枚举子树节点,第二层(j)枚举当前节点x的子节点,第三层(k)从m - 1枚举到1(即枚举背包容量,m - 1是因为x这个点必须要选),第四层(l)从0枚举到k(即给当前儿子用多少容量)。
#include <iostream>
#include <cstdio>
#define N 305
using namespace std;
struct E {int next, to;} e[N];
int n, m, num;
int h[N], in[N], w[N];
int dp[N][N];
bool vis[N];
void add(int u, int v)
{
e[++num].next = h[u];
e[num].to = v;
h[u] = num;
}
void dfs(int x)
{
vis[x] = 1;
for(int i = h[x]; i != 0; i = e[i].next)
if(!vis[e[i].to]) dfs(e[i].to);
for(int i = h[x]; i != 0; i = e[i].next)
{
int now = e[i].to;
for(int j = m - w[x]; j >= w[now]; j--)
for(int k = 0; k <= j; k++)
dp[x][j + w[x]] = max(dp[x][j + w[x]], dp[x][j + w[x] - k] + dp[now][k]);
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
int to;
cin >> to >> dp[i][1];
w[i] = 1, add(to, i);
}
dfs(0);
cout << dp[0][m];
return 0;
}