[oiclass1454]选课:树上背包

题目

在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 N 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程 a 是课程 b 的先修课即只有学完了课程 a,才能学习课程 b)。一个学生要从这些课程里选择 M 门课程学习,问他能获得的最大学分是多少?

输入

第一行有两个整数 N,M 用空格隔开。(1N300,1M200)
接下来的 N 行,第 i+1 行包含两个整数 kisi, ki 表示第 i 门课的直接先修课,si 表示第 i 门课的学分。若 ki=0 表示没有直接先修课(1kiN,1si20)。

输出

只有一行,选 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];
}
posted @   chxulong  阅读(104)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示