题目
在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 N 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程 a 是课程 b 的先修课即只有学完了课程 a,才能学习课程 b)。一个学生要从这些课程里选择 M 门课程学习,问他能获得的最大学分是多少?
输入输出
输入:第一行输入N,M。下面N行输入两个整数ki和si,分别为第i门课的先修课序号,第i门课的学分。
输出:选M门课的最大学分
思路
这道题输入的是森林,并不是一棵树,需要加一个结点,把每个子树连到根节点,构成一棵树,即加入0结点作为虚拟课程,学分为0。
状态转移数组dp[i][j]
为以i为根的子树选j门课程获得的最大学分。
0结点是必选的,所以需要m+1。
状态转移方程dp[u][i](i>=1)
初始化为s[u]为u的学分。dp[u][j] = max(dp[u][j],dp[v][k] + dp[u][j-k]);
#include <iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int MAX_N = 310; //最多结点数
int tot; //标记边的序号
int head[MAX_N],nxt[MAX_N],ver[MAX_N]; //建树要用到的数组
int dp[MAX_N][MAX_N];
int n,m,s[MAX_N];
void addedge(int u,int v){ //根据邻接表建树的过程
ver[++tot] = v;
nxt[tot] = head[u];
head[u] = tot;
}
void dfs(int u){
dp[u][0] = 0;
for(int i = 1;i < m;i++) dp[u][i] = s[u]; //初始化都加上本身的学分
for(int i = head[u];i;i = nxt[i]){
int v = ver[i];
dfs(v);
for(int j = m;j >= 1;j--){ //逆序枚举背包容量
for(int k = j - 1;k >= 0;k--){ //枚举给子树分配多少容量
dp[u][j] = max(dp[u][j],dp[v][k] + dp[u][j-k]); //子树选k门课获得的学分加上本身选j-k门课获得的学分
}
}
}
}
int main(){
cin >> n >> m;
int u;
for(int i=1;i <= n;i++){
cin >> u >> s[i];
addedge(u,i);
}
m++; //将0这门虚拟课程也算进去了
dfs(0);
cout << dp[0][m] ;
return 0;
}