选课

本文介绍了一种使用树形动态规划(Tree DP)解决课程选修问题的方法,旨在帮助学生在满足先修课程条件的前提下,最大化所获得的学分。文章详细解释了如何将课程之间的依赖关系构建成树形结构,并提供了两种实现方案:多叉树和二叉树。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

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

输入描述 Input Description
输入文件的第一行包括两个整数N、M(中间用一个空格隔开)其中1≤N≤300,1≤M≤N。
以下N行每行代表一门课。课号依次为1,2,…,N。每行有两个数(用一个空格隔开),第一个数为这门课先修课的课号(若不存在先修课则该项为0),第二个数为这门课的学分。学分是不超过10的正整数。

输出描述 Output Description
输出文件只有一个数,实际所选课程的学分总数。

样例输入 Sample Input
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2

样例输出 Sample Output
13
两种方法,多叉树和二叉树。

#include<iostream>
using namespace std;
struct data{
    int f,to,next;
}a[305];
int value[305];
int head[305];int len=0;int n,m;
void connect(int x,int y)
{  a[++len].f=x;a[len].to=y;
   a[len].next=head[x];
   head[x]=len;
}
int f[305][305];
int size[305];
void dfs(int x,int from)
{  size[x]=1;
   for (int i=head[x];i;i=a[i].next)
   {  if (a[i].to!=from)
        {dfs(a[i].to,x);
        size[x]+=size[a[i].to];
        }
   }
}
void dp(int x,int from)
{  for (int i=head[x];i;i=a[i].next)
   {  dp(a[i].to,x);
      for (int j=min(m,size[x]);j>=0;--j)
      {  for (int k=0;k<j&&k<size[a[i].to];++k)
           f[x][j]=max(f[x][j],f[x][j-k-1]+f[a[i].to][k]+value[a[i].to]);
      }
   }
}
int main()
{   
    cin>>n>>m;
    for (int i=1;i<=n;++i)
    {   int x,y;
       cin>>x>>value[i];
       connect(x,i);
    }
    dfs(0,0);
    dp(0,0);
    cout<<f[0][m];
}

这的确是树DP的经典了,树DP的基本知识都挂上了(当然,至于最大直径一类拓展,这题还是没用到……)。

我们用f[i,j]表示到第i个节点分配j的代价所能达到的最大价值。

首先包括一个建树过程,每一个节点就代表这一个课程,节点的父亲就是该课程的先修课程。因为对于某一门课程只有一门先修课,所以不会有一个节点两个父亲的局面,所以就可以放放心心地建树。

接着我们就要处理一个非二叉树转换为二叉树的问题。

我们把所有节点的兄弟变为它的右儿子,它的儿子变为它的左儿子,这样就把一棵多叉树变成了一棵二叉树。

当然DP方程式也有变化。

因为此时选择当前节点的左儿子就必须选择当前的节点,而选择当前节点的右儿子不必选择当前节点。所以DP方程为:

f[i,j]=max(f[r,j],f[l,k]+f[r,j-k-cost[i]]+w[i]),其中0<=k<=j-cost[i]。

当然这里还有一个注意点:

我们发现数据时可以构成一个森林的,那么我们就需要一个虚拟节点来连接所有的树的根节点。

我相信某些懒人,像我一样直接用0来作为虚拟节点。

这样我们在求的最终答案的时候,如果用f[0,i]来求就会得出错误答案。

为什么呢?

看到我们的DP方程式。当k>0的时候,我们发现之前的f0,l已经有值了(也就是不等于0)。那么这个时候我们求出的f[0,k]事实上就累加了之前某一个f[0,l]的值。

那为什么在其他节点不会有这样的问题呢?因为在其他节点求状态最优的时候,f0,l一直是0,所以不会影响答案。

所以,我们在求得答案的时候,就用f[tree[0].l,m]来求就好了。

#include<iostream>
#include<cstring>
using namespace std;int n,m;
struct data{
    int l,r;
}g[305];
int f[305][305];
int value[305];
void connect(int x,int y)
{  if (!g[x].l)
     g[x].l=y;
   else
   {  int u=g[x].l;
      while (g[u].r)
        u=g[u].r;
      g[u].r=y;
   }
}
void dfs(int x)
{  if (g[x].l)
     dfs(g[x].l);
    if (g[x].r)
      dfs(g[x].r);
   for (int i=0;i<=m;++i)
     for (int k=0;k<i;++k)
       f[x][i]=max(f[x][i],max(f[g[x].l][i-k-1]+value[x]+f[g[x].r][k],f[g[x].r][i]));
}
int main()
{  
   cin>>n>>m;
   for (int i=1;i<=n;++i)
   {  int x,y;
      cin>>x>>value[i];
      connect(x,i);
   }
   dfs(0);
   cout<<f[g[0].l][m];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值