[CTSC1997] 选课
题目描述
在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 N N N 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程 a 是课程 b 的先修课即只有学完了课程 a,才能学习课程 b)。一个学生要从这些课程里选择 M M M 门课程学习,问他能获得的最大学分是多少?
输入格式
第一行有两个整数 N N N , M M M 用空格隔开。( 1 ≤ N ≤ 300 1 \leq N \leq 300 1≤N≤300 , 1 ≤ M ≤ 300 1 \leq M \leq 300 1≤M≤300 )
接下来的 N N N 行,第 I + 1 I+1 I+1 行包含两个整数 $k_i $和 s i s_i si, k i k_i ki 表示第I门课的直接先修课, s i s_i si 表示第I门课的学分。若 k i = 0 k_i=0 ki=0 表示没有直接先修课( 1 ≤ k i ≤ N 1 \leq {k_i} \leq N 1≤ki≤N , 1 ≤ s i ≤ 20 1 \leq {s_i} \leq 20 1≤si≤20)。
输出格式
只有一行,选 M M M 门课程的最大得分。
样例 #1
样例输入 #1
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
样例输出 #1
13
大致思路
每门课的直接先修课最多只有一门,将每门课的先修课与其相连,将会构成一颗或者多颗树。为此我们可以建立一个 0 号节点,将所有树的根节点都连向 0 号节点。0 号节点的学分为 0,并且允许选修的课数量要自加一。
将森林拼接成一棵树之后我们就需要对这一颗树进行树上的动态规划了。
考虑树上的每一个节点:这个节点可能是一个或者多个节点的父节点(先修课),也有可能没有。用 f[i][j] 表示对于节点 i , 一共选择 j 门课所能得到的最大学分(j 门科目中包括他本身)。选择他的所有子节点的前提是一定要选择这个节点,所 f[i][1] 一定等于 i 节点的权值(学分)
按照一定的顺序遍历 i 节点的所有子节点。当遍历到 x 节点的时候,我们假设已经得到了所有 f[x][k] 的值。由于 f[i][k] 中是没有 x 节点及其子树的权值的,所以可以通过 x 节点来更新 i 节点的 f 数组。即 f[i][k] = max(f[i][k], f[i][k - p] + f[x][p]) (1 ≤ k ≤ m , 0 ≤ p < k)
p < k 的原因是当 p = k 的时候 f[i][k - p] 中 k - p 的值等于 0,这说明 i 这个节点无法被选择到,由于 i 是 x 节点的先修课,不选择 i 节点便无法选择 x 节点,所以应满足 p < k
在枚举 k 的时候需要注意:由于 f[i][k] 由 f[i][k - p] 更新得到,那么就需要保证更新 f[i][k] 的时候 f[i][k - p] 没有被更新,由于 p 是一个正整数,所以我们倒序枚举 k 即可
AC CODE
#include<bits/stdc++.h>
using namespace std;
const int INF=0x7f7f7f7f;
int n,m;
int f[311][313],w[333];
vector<int> v[400];
void dfs(int u){
//cout<<u<<endl;
f[u][0]=0;
f[u][1]=w[u];
for(int i=2;i<=m+1;i++){
f[u][i]=-213123123;
}
for(int i=0;i<v[u].size();i++){
int tmpv=v[u][i];
dfs(tmpv);
for(int j=m+1;j>=1;j--){
for(int k=0;k<j;k++){
f[u][j]=max(f[u][j],f[tmpv][k]+f[u][j-k]);
}
}
}
}
int main(){
cin>>n>>m;
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++){
int k,vv;
cin>>k>>vv;
if(k==0) v[n+1].push_back(i);
else v[k].push_back(i),w[i]=vv;
w[i]=vv;
}
w[n+1]=0;
dfs(n+1);
cout<<f[n+1][m+1];
return 0;
}