『选课 有树形依赖的背包问题』

『选课 有树形依赖的背包问题』

<更新提示>

<第一次更新>


<正文>

选课(tyvj 1051)

Description

学校实行学分制。每门的必修课都有固定的学分,同时还必须获得相应的选修课程学分。学校开设了N(N < 300)门的选修课程,每个学生可选课程的数量M是给定的。学生选修了这M门课并考核通过就能获得相应的学分。

在选修课程中,有些课程可以直接选修,有些课程需要一定的基础知识,必须在选了其它的一些课程的基础上才能选修。例如《Frontpage》必须在选修了《Windows操作基础》之后才能选修。我们称《Windows操作基础》是《Frontpage》的先修课。每门课的直接先修课最多只有一门。两门课也可能存在相同的先修课。每门课都有一个课号,依次为1,2,3,…。
你的任务是为自己确定一个选课方案,使得你能得到的学分最多,并且必须满足先修课优先的原则。假定课程之间不存在时间上的冲突。

Input Format

输入文件的第一行包括两个整数N、M(中间用一个空格隔开)其中1≤N≤300,1≤M≤N。

以下N行每行代表一门课。课号依次为1,2,…,N。每行有两个数(用一个空格隔开),第一个数为这门课先修课的课号(若不存在先修课则该项为0),第二个数为这门课的学分。学分是不超过10的正整数。

Output Format

输出文件只有一个数,实际所选课程的学分总数。

Sample Input

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

Sample Output

13

解析

由题意可得,课程与课程之间的先修关系构成了一棵树,在最优化问题中,我们通常是用树形\(DP\)来求解的。那么我们可以先尝试树形\(DP\)的方法:设\(f[i][j]\)代表以\(i\)为根的子树中选了\(j\)门课程的最大学分数。显然,这个状态的转移在每颗子树中都可以选取若干门课程,那么,我们首先可以得出如下的状态转移方程:

\[f[i][j]=\max_{\sum^{|son(i)|}_{k=1}c_k=j-1}\{\sum^{|son(i)|}_{k=1}f[son_k][c_k]\}+score[i] \]

其中\(|son(i)|\)代表\(i\)的子节点个数,\(son_k\)代表其中某个子节点的编号,\(c_k\)则代表对于每一颗子树选取的课程门数,需满足:\(\sum_{k=1}^{|son(i)|}c_k=j-1\)
事实上,这是一个分组背包模型:\(j-1\)即为背包体积,每一个\(son_k\)即为一组物品,每组组物品分别有\(j-1\)个,每一个体积分别为\(c_k\),价值为\(f[son_k][c_k]\),而每组物品我们只能从中选取至多一个,使得总价值最大。
那么对于每一颗子树,我们就可以用分组背包的方程来转移了。

\[f[i][j]=\max\{f[i][t-j]+f[son][j]\} \]

注意一个细节,没有先修课的课程,在输入中我们认为它的先修课是一个虚拟课程\(0\),那么在\(i=0\)时,背包的体积为\(j\),不需要为该门课程本身留下一个位置,所以我们可以将带有这门课程本身的状态额外的转移一次。

\(Code:\)

#include<bits/stdc++.h>
using namespace std;
const int N=300+30,M=300+30;
int n,m,f[N][M],score[N]; 
vector < int >Link[N];
inline void input(void)
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		Link[u].push_back(i);
		score[i]=v;
	}
}
inline void dp(int root)
{
	f[root][0]=0;
	for(int i=0;i<Link[root].size();i++)
	{
		int Son=Link[root][i];
		dp(Son);
		for(int v=m;v>0;v--)
		{
			for(int j=1;j<=v;j++)
			{
				f[root][v]=max(f[root][v],f[Son][j]+f[root][v-j]);
			}
		}
	}
	if(root!=0)
		for(int v=m;v>0;v--)
			f[root][v]=f[root][v-1]+score[root];
}
int main(void)
{
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	input();
	dp(0);
	printf("%d\n",f[0][m]);
}



<后记>

posted @ 2019-01-29 13:42  Parsnip  阅读(442)  评论(0编辑  收藏  举报