『选课 有树形依赖的背包问题』
<更新提示>更新提示>
<第一次更新>第一次更新>
<正文>正文>
选课(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]);
}
<后记>后记>