<更新提示>
<第一次更新>这道题的树上分组背包的做法已经在『选课 有树形依赖的背包问题』中讲过了,本篇博客中主要讲解将多叉树转二叉树的做法,以便输出方案。
<正文>
选课
Description
学校实行学分制。每门的必修课都有固定的学分,同时还必须获得相应的选修课程学分。学校开设了N(N < 500)门的选修课程,每个学生可选课程的数量M是给定的。学生选修了这M门课并考核通过就能获得相应的学分。
在选修课程中,有些课程可以直接选修,有些课程需要一定的基础知识,必须在选了其它的一些课程的基础上才能选修。例如《Frontpage》必须在选修了《Windows操作基础》之后才能选修。我们称《Windows操作基础》是《Frontpage》的先修课。每门课的直接先修课最多只有一门。两门课也可能存在相同的先修课。每门课都有一个课号,依次为1,2,3,…。
上例中1是2的先修课,即如果要选修2,则1必定已被选过。同样,如果要选修3,那么1和2都一定已被选修过。 你的任务是为自己确定一个选课方案,使得你能得到的学分最多,并且必须满足先修课优先的原则。假定课程之间不存在时间上的冲突。
Input Format
第一行包括两个正整数N、M(中间用一个空格隔开)其中N表示待选课程总数(1≤N≤500),M表示学生可以选的课程总数(1≤M≤N)。 以下M行每行代表一门课,课号依次为1,2,…,M。每行有两个数(用一个空格隔开),第一个数为这门课的先修课的课号(若不存在先修课则该项为0),第二个数为这门课的学分。
Output Format
第一行只有一个数,即实际所选课程的学分总数。 以下N行每行有一个数,表示学生所选课程的课号。
n行学生选课的课号按从小到大的顺序输出。
Sample Input
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
Sample Output
13
2
3
6
7
解析
直接使用树上背包的方法,不便于输出方案,我们换一种思路(dp)。对于每一个节点,我们记录(br[i])代表(i)一个兄弟节点的编号,(ch[i])代表(i)一个子节点的编号。如果兄弟节点和子节点不止一个怎么办,显然,它兄弟的兄弟也是它的兄弟,它儿子的兄弟也是它的儿子,这样就可以表示所有的兄弟节点和儿子节点了,而事实上,对于每一个(i)我们只记录一个(br[i])和(ch[i]),这样,本质上我们就把多叉树转换为二叉树了。
还是设(f[x][t])代表以(x)为根的子树中选了(t)门课的最大学分,显然有两种转移:
(1.) 不取节点(x),直接令(f[x][t]=f[br[x]][t])即可
(2.) 取节点(x)并在以(x)为根的子树中取一部分点,剩下的一部分点在兄弟中取,即(f[x][t]=max{f[br[x]][i]+f[ch[x]][t-i-1]+a[x]})
利用上述两个方程即可完成树形(dp)。
我们可以通过同样的枚举方式得知每一次节点(x)是否被选,就能得到方案了。
(Code:)
#include<bits/stdc++.h>
using namespace std;
const int N = 520;
int n,m,br[N],ch[N],a[N],f[N][N],ans[N];
inline void input(void)
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
int fa; scanf("%d%d",&fa,&a[i]);
if ( fa == 0 ) fa = n+1;
br[i] = ch[fa];
ch[fa] = i;
}
}
inline void dp(int x,int t)
{
if ( f[x][t] > 0 ) return;
if ( x == 0 || t == 0 ) return;
dp( br[x] , t );
f[x][t] = f[br[x]][t];
for (int i=0;i<t;i++)
{
dp( br[x] , i ) , dp( ch[x] , t-i-1 );
f[x][t] = max( f[x][t] , f[br[x]][i] + f[ch[x]][t-i-1] + a[x] );
}
}
inline void solve(int x,int t)
{
if ( x == 0 || t == 0 ) return;
if ( f[x][t] == f[br[x]][t] ) return solve( br[x] , t );
for (int i=0;i<t;i++)
{
if ( f[x][t] == f[br[x]][i] + f[ch[x]][t-i-1] + a[x] )
{
solve( br[x] , i ) , solve( ch[x] , t-i-1 );
ans[x] = true; break;
}
}
}
int main(void)
{
input();
dp( ch[n+1] , m );
solve( ch[n+1] , m );
printf("%d
",f[ch[n+1]][m]);
for (int i=1;i<=n;i++)
if ( ans[i] )
printf("%d
",i);
return 0;
}
<后记>