CH5402 选课

https://ac.nowcoder.com/acm/contest/1044/B

题目

学校实行学分制。

每门的必修课都有固定的学分,同时还必须获得相应的选修课程学分。

学校开设了 N 门的选修课程,每个学生可选课程的数量 M 是给定的。

学生选修了这 M 门课并考核通过就能获得相应的学分。

在选修课程中,有些课程可以直接选修,有些课程需要一定的基础知识,必须在选了其他的一些课程的基础上才能选修。

例如《Windows程序设计》必须在选修了《Windows操作基础》之后才能选修。

我们称《Windows操作基础》是《Windows程序设计》的先修课。

每门课的直接先修课最多只有一门。

两门课可能存在相同的先修课。

你的任务是为自己确定一个选课方案,使得你能得到的学分最多,并且必须满足先修条件。

假定课程之间不存在时间上的冲突。

题解

设$dp[r][c]$为以$r$为根,选择$c$门课的最多学分

那么

[dp[r][c]=maxleft{dp[s_i][c_i]|(sum_{i=1}^{p} c_i)=c-1}+v[r]]

然而这个式子看起来像是废话,只靠这个是不能写出时限内的转移的($O(2^n)$)

还是需要利用背包,考虑某个节点的n个子树,把上一个式子扩展一下

[dp[r][cnt][c]=max{dp[r][cnt-1][c-c_i]+dp[s_i][n_s][c_i]}+v[r]],可以滚动优化[cnt],枚举$c_s$,时间复杂度$O(n*m*m)$

AC代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define REP(i,a,b) for(register int i=(a); i<(b); i++)
#define REPE(i,a,b) for(register int i=(a); i<=(b); i++)
#define PERE(i,a,b) for(register int i=(a); i>=(b); i--)
using namespace std;
int n,m;
int hd[307], nxt[307], to[307], e=0, v[307];
inline void adde(int a, int b) { nxt[e]=hd[a]; hd[a]=e; to[e]=b; e++; }
int dp[307][307];
void calc(int r) {
	for(int i=hd[r]; ~i; i=nxt[i]) {
		int s=to[i];
		calc(s);
		PERE(c,m,0)
		REPE(k,0,c) {
			dp[r][c]=max(dp[r][c],dp[r][c-k]+dp[s][k]);
		}
	}
	if(r) PERE(c,m,1) {
		dp[r][c]=dp[r][c-1]+v[r];
	}
}
int main() {
	memset(dp,0,sizeof(dp));
	memset(hd,-1,sizeof hd);
	scanf("%d%d", &n, &m);
	REPE(i,1,n) {
		int a;
		scanf("%d%d", &a, &v[i]);
		adde(a,i);
	}
	calc(0);
	printf("%d
", dp[0][m]);
}
原文地址:https://www.cnblogs.com/sahdsg/p/12459730.html