『选课 树形dp 输出方案』

<更新提示>

<第一次更新>这道题的树上分组背包的做法已经在『选课 有树形依赖的背包问题』中讲过了,本篇博客中主要讲解将多叉树转二叉树的做法,以便输出方案。


<正文>

选课

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\n",f[ch[n+1]][m]);
    for (int i=1;i<=n;i++)
        if ( ans[i] )
            printf("%d\n",i);
    return 0;
}


<后记>

posted @ 2019-06-10 20:46  Parsnip  阅读(560)  评论(0编辑  收藏  举报