• [CTSC2001]1378 选课


     

    1378 选课

    题目描述

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

      在选修课程中,有些课程可以直接选修,有些课程需要一定的基础知识,必须在选了其它的一些课程的基础上才能选修。例如《Frontpage》必须在选修了《Windows操作基础》之后才能选修。我们称《Windows操作基础》是《Frontpage》的先修课。每门课的直接先修课最多只有一门。两门课也可能存在相同的先修课。每门课都有一个课号,依次为1,2,3,…。 例如: 

    【详见图片】
    表中1是2的先修课,2是3、4的先修课。如果要选3,那么1和2都一定已被选修过。   你的任务是为自己确定一个选课方案,使得你能得到的学分最多,并且必须满足先修课优先的原则。假定课程之间不存在时间上的冲突。

    输入输出格式

    输入格式:

    第一行有两个整数N,M用空格隔开。(1<=N<=200,1<=M<=150)

    接下来的N行,第I+1行包含两个整数ki和si, ki表示第I门课的直接先修课,si表示第I门课的学分。若ki=0表示没有直接先修课(1<=ki<=N, 1<=si<=20)。

     
    输出格式:

    只有一行,选M门课程的最大得分。

    输入输出样例

    输入样例#1:
    7  4
    2  2
    0  1
    0  4
    2  1
    7  1
    7  6
    2  2
    
    输出样例#1:
    13
    数据范围及提示 Data Size & Hint

    各个测试点1s

    分类标签 Tags 点此展开 

     
    典型的树形背包(dp),希望读者能够认真理解并掌握。

    分析:

    根据题目描述我们可以知道这是个树形动规问题,但由于一个点可以有多个儿子,很难写出方程,所以我们想到将森林转二叉,之后再去做。

    这里先补充一下多叉转二叉的知识,其规则是左儿子,右兄弟,即左子树上的都是儿子节点,右子树上的都是兄弟节点。这道题是森林,我们只需要虚拟零节点即可。我们要搜索的根就是f[0].left;

    转化为二叉树之后,我们很容易就能写出方程f[i,j]表示以i为根的树,选了j门课能够得到的最大学分。

    f[i,j]:=max{f[tree[i].left,k]+f[tree[i].right,j-1-k]+a[i],f[tree[i].right,j]}只和当前这门课选不选有关,这是很显然的,完全根据我们转换出的二叉树的定义和题目要求得来。

    AC代码

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    using namespace std;
    #define N 1010
    int n,m,f[N][N];
    struct node{
        int l,r,num;
    }tree[N];
    void init(){
        scanf("%d%d",&n,&m);
        for(int i=1,x;i<=n;i++){
            scanf("%d%d",&x,&tree[i].num);
            if(!tree[x].l) tree[x].l=i;
            else{
                int t=tree[x].l;
                while(tree[t].r) t=tree[t].r;
                tree[t].r=i; 
            } 
        }
    }
    int dfs(int i,int m){
        int tmp1,tmp2;
        if(f[i][m]!=-1) return f[i][m];
        if(!i||!m) return f[i][m]=0;
        tmp1=dfs(tree[i].r,m);
        for(int j=0;j<m;j++){
            tmp2=dfs(tree[i].l,j)+dfs(tree[i].r,m-j-1)+tree[i].num;
            if(tmp1<tmp2) tmp1=tmp2;
        }
        return f[i][m]=tmp1;
    }
    int main(){
        memset(f,-1,sizeof f);
        init();
        printf("%d
    ",dfs(tree[0].l,m));
        return 0;
    }

     -----------------------------------------------------------------------------------------------------------------------------------------------

    华丽的分割线

    ------------------------------------------------------------------------------------------------------------------------------------------------

    反思:

    在树的儿子很多,很难处理时候,我们可以考虑多叉转二叉,或者森林转二叉。简化方程,只和当前节点选或者不选有关。特别注意,这样转的话会加大树的深度,也就是我们压栈的次数,数据范围极大时慎用。

    记忆化搜索是解决树归的利器,注意记忆化搜索的框架,和打法。一定要注意细节。在函数中应有当前状态是否已知的判断,边界条件的处理(注意给数组赋值),根据方程枚举状态找最优,最后将求出的最优赋到数组中再返回值。

  • 相关阅读:
    Android的各版本间的区别总结
    深入浅出Android开发之Surface介绍
    android中完全退出当前应用程序的四种方法
    android离线地图源码
    坐标系
    mysql安装
    linux磁盘空间清理
    HttpClient教程
    TIME_WAIT过多
    c3p0配置详解
  • 原文地址:https://www.cnblogs.com/shenben/p/5638138.html
Copyright © 2020-2023  润新知