zoukankan      html  css  js  c++  java
  • sss

    <更新提示>

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


    <正文>

    选课

    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;
    }
    
    

    <后记>

  • 相关阅读:
    v-if和v-show的区别
    关于C语言静态链接的个人理解,欢迎指正
    关于C语言中的强符号、弱符号、强引用和弱引用的一些陋见,欢迎指正
    Android: ScrollView监听滑动到顶端和底端
    Android小记之--ClickableSpan
    Android小记之--android:listSelector
    表达式参数
    Http和Socket连接
    Hibernate: merge方法
    Android小代码——设置全屏
  • 原文地址:https://www.cnblogs.com/Parsnip/p/11000127.html
Copyright © 2011-2022 走看看