P2014 [CTSC1997] 选课(树形dp)

[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 1N300 , 1 ≤ M ≤ 300 1 \leq M \leq 300 1M300 )

接下来的 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 1kiN , 1 ≤ s i ≤ 20 1 \leq {s_i} \leq 20 1si20)。

输出格式

只有一行,选 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;
}

附封面

请添加图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值