[oiclass1454]选课:树上背包

题目

在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 \(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];
}
上一篇:四边形不等式


下一篇:【题解】ARC100D Colorful Sequences | 20211215 模拟赛 序列【DP】