洛谷P2014 [CTSC1997]选课

题目描述

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

输入格式

第一行有两个整数 \(N\) , \(M\) 用空格隔开。( \(1 \leq N \leq 300\) , \(1 \leq M \leq 300\) )

接下来的 \(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\) 门课程的最大得分。

输入输出样例

输入 #1

7  4
2  2
0  1
0  4
2  1
7  1
7  6
2  2

输出 #1

13

思路

这个题确实比较明显是一道树形dp,课与先修课之间的关系很明显是父节点与子节点之间的关系。用二维数组\(f_{i,j}\)表示以\(i\)为根节点的子树选\(t\)门课的最大得分。下面考虑状态转移方程,然后会猛然发现,这好像是一个分组背包?!\(t\)门课相当于背包的容积,而子树就是分组。

但是dp要有根节点,我们并不知道选哪个课会最优,也就是无法确定根节点,那么我们就自己设一个虚拟根节点0,作为实际没有先修课的课程的先修课。这样0号结点就成为了整棵树的根节点,从0开始dp即可。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<cstdlib>
#include<ctime>
using namespace std;
int n,m,score[310];
vector<int> son[310];
int fa[310];
int f[310][310];//f[x][t]表示在以x为根的子树内选t门课程的最大得分 
void dp(int x){
	f[x][0]=0;//选0门课肯定得分为0 
	for(int i=0;i<son[x].size();i++){
		int y=son[x][i];
		dp(y);//遍历子树并递归 
		for(int t=m;t>=0;t--){//循环背包容积
			for(int j=t;j>=0;j--){//循环组内物品
				f[x][t]=max(f[x][t],f[x][t-j]+f[y][j]);
			}
		}
	}
	if(x!=0){//如果不是虚拟的0结点,那么还要更新
		for(int t=m;t>0;t--){
			f[x][t]=f[x][t-1]+score[x];
		}
	}//如果到了叶子结点那么只有这一层循环,相当于背包所以倒着循环。如果不是叶子结点也要更新,因为根节点必须选 
}
int main()
{
	scanf("%d%d",&n,&m);
	memset(fa,-1,sizeof(fa));
	for(int i=1,op;i<=n;i++){
		scanf("%d%d",&op,&score[i]);
		if(op!=0){
			son[op].push_back(i);//建树 
			fa[i]=op;
		}
	}
	for(int i=1;i<=n;i++){
		if(fa[i]==-1){
			son[0].push_back(i);//找到根节点 
			fa[i]=0;
		}
	}
	dp(0);//dp 
	printf("%d\n",f[0][m]);//输出以虚拟结点0为根节点的子树选m门课的最大得分 
	return 0;
}
posted @ 2020-08-12 10:11  徐明拯  阅读(117)  评论(0编辑  收藏  举报