JZOJ 3418. 【NOIP动态规划专题】选课

本文介绍了一种解决课程选修问题的算法,利用树形动态规划来找到学生选课的最大总学分方案。文章详细阐述了如何将多叉树转化为二叉树进行处理,以及如何设计状态转移方程来求解最大学分。

Description

大学里实行学分。每门课程都有一定的学分,学生只要选修了这门课,并通过考核就能获得相应的学分。学生最后的学分是他各门课学分的总和。每个学生都要选择规定数量的课程。其中有些课程可以直接选修,有些课程需要一定的基础知识,必须在选了其他的一些课程的基础上才能选修。

例如,《剥皮术》就必须在选修了《屠龙术》后才能选修。

我们称《屠龙术》是《剥皮术》的先修课。

每门课的直接先修课最多之有一门。两门课也可能存在相同的先修课。

每门课都有一个课号,课号依次是1,2,3……。以下表为例说明。

课号 先修课号 学分

1 无 1

2 1 1

3 2 3

4 无 3

5 2 4

上表中1是2的先修课,即如果要选修2,则1必须已被选过。

同样,要选修3,那么1和2都一定被选修过。

每个学生可选的课程总数是一定的,请找出一种方案,使得到的总学分最多。

Input

第一行包括两个正整数M、N(中间一个空格),其中M表示总课程数(1<=M<=1000),N表示每个学生最多可选的课程总数。(1<=N<=M)。

以下M行每行代表一门课,课号依次是1,2,…,M。每行两个数,第一个数为这门课的直接先修课的课号(若不存在则为0),第二个数为该门课的学分。学分是不超过10的正整数。

测试数据保证学生能够选满N门课。

Output

第一行只有一个数,即最多可得的学分。

如果M<=99,则以下N行每行一个数,表示学生所选的课程的课号,课号按升序排列。

如果M>=100,则不必输出具体方案。

数据保证只有唯一的正确输出。

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

Data Constraint

1<=M<=1000

Solution

  • 先按照题目所描述的关系建树(多叉树转化成二叉树),以方便处理。

  • 多叉转二叉 的方法: 一个点的左儿子存其真儿子,右儿子存其兄弟节点

  • 接着就是我们的树形DP了——

  • F[i][j] 表示当前做到第 i 门课、在以其为根的子树中选了 j 门课的最大学分。转移为:

    F[i][j]=Max{F[l[i]][k1]+F[r[i]][jk]}1kjlr
  • 统计具体方案就用一个邻接表储存,当最优方案更新时随之更新即可。

  • 总时间复杂度为 O(M2)

Code

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1001,M=101;
int m,n,ans;
int a[N],b[N],l[N],r[N];
int f[N][N],g[M][M][M];
inline int read()
{
    int X=0,w=1; char ch=0;
    while(ch<'0' || ch>'9') {if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0' && ch<='9') X=(X<<3)+(X<<1)+ch-'0',ch=getchar();
    return X*w;
}
inline void dfs(int x,int y)
{
    if(!x || !y) return;
    dfs(r[x],y);
    if(m<=99)
        for(int i=0;i<=g[r[x]][y][0];i++) g[x][y][i]=g[r[x]][y][i];
    int sum=f[r[x]][y];
    for(int i=1;i<=y;i++)
    {
        dfs(l[x],i-1);
        dfs(r[x],y-i);
        if(f[l[x]][i-1]+f[r[x]][y-i]+a[x]>sum)
        {
            sum=f[l[x]][i-1]+f[r[x]][y-i]+a[x];
            if(m<=99)
            {
                g[x][y][g[x][y][0]=1]=x;
                for(int j=1;j<=g[l[x]][i-1][0];j++) g[x][y][++g[x][y][0]]=g[l[x]][i-1][j];
                for(int j=1;j<=g[r[x]][y-i][0];j++) g[x][y][++g[x][y][0]]=g[r[x]][y-i][j];
            }
        }
    }
    f[x][y]=sum;
}
int main()
{
    m=read(),n=read();
    for(int i=1;i<=m;i++)
    {
        int x=read();
        if(!l[x]) l[x]=i; else r[b[x]]=i;
        a[b[x]=i]=read();
    }
    dfs(l[0],n);
    printf("%d\n",f[l[0]][n]);
    if(m<=99)
    {
        for(int i=1;i<=n;i++) b[i]=g[l[0]][n][i];
        sort(b+1,b+1+n);
        for(int i=1;i<=n;i++) printf("%d\n",b[i]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值