题目
在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 \(N\) 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程 \(a\) 是课程 \(b\) 的先修课即只有学完了课程 \(a\),才能学习课程 \(b\))。一个学生要从这些课程里选择 \(M\) 门课程学习,问他能获得的最大学分是多少?
输入
第一行有两个整数 \(N,M\) 用空格隔开。(\(1\leq N \leq 300,1\leq M\leq 200\))
接下来的 \(N\) 行,第 \(i+1\) 行包含两个整数 \(k_i\) 和 \(s_i\), \(k_i\) 表示第 \(i\) 门课的直接先修课,\(s_i\) 表示第 \(i\) 门课的学分。若 \(k_i=0\) 表示没有直接先修课(\(1\leq k_i \leq N, 1\leq s_i \leq 20\))。
输出
只有一行,选 \(M\) 门课程的最大得分。
输入样例
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
输出样例
13
题解
经典题,传统解法是将讲多叉树转化成二叉树,然后在二叉树上进行树形DP,现在使用树上背包来解会更简单。
需要事先学习树上背包,理解分组背包的求法,理解如何将问题转化成树上背包问题。
本题有两种定义方式。
1
定义\(f[i][j]\)表示以i为根选择j门课程的最大得分(包含i的结点),代码如下:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=300+5;
vector<int> g[N];
int n,m,u,v,w,f[N][N],s[N];
void dfs(int u){
for(int i=1;i<=m;i++)f[u][i]=s[u];
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
dfs(v);
for(int j=m;j>=0;j--){
for(int k=0;k<j;k++){
f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]);
}
}
}
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1,x;i<=n;i++){
scanf("%d %d",&x,&s[i]);
g[x].push_back(i);
}
m++;
dfs(0);
printf("%d",f[0][m]);
}
2
定义\(f[i][j]\)表示以i为根选择j门课程的最大得分(不包含i的结点),代码如下:
点击查看代码
#include<iostream>
#include<vector>
using namespace std;
vector<int> g[301];
int n,m,x,s[301],f[301][201];
void dfs(int u){
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
dfs(v);
for(int j=m;j>=0;j--){
for(int k=0;k<j;k++){
f[u][j]=max(f[u][j],f[u][j-k-1]+f[v][k]+s[v]);
}
}
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>x>>s[i];
g[x].push_back(i);
}
dfs(0);
cout<<f[0][m];
}