有依赖的背包问题——选课

描述 Description

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

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

有依赖的背包问题——选课 - 游侠UFO - 游侠UFO工作室

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

输入格式 Input Format

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

输出格式 Output Format

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

样例输入 Sample Input

7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2

样例输出 Sample Output

13

[分析]

有些课程可能有先修课且最多只有一门先修课,而这些有先修课的课程必须是选了先修课才能选,求限选M课的情况下所能达到的最大学分。题目一读完,显然可以反应出这是一个带有依赖的背包问题。因为每门课程最多只有一门先修课程,所以这些课程的依赖关系可以由森林来表示。这样我们就可以把森林中的每一棵树看成是一个泛化物品(具体介绍见:http://ufownl.blog.163.com/blog/static/1250122200931174848906/),这样我们就可以对这些泛化物体作01背包从而得到最优解。先设h[i][k]表示泛化物体i在花费代价k时所能得到的最大价值,再设s[i][j]表示仅考虑前i件泛化物体且背包容量为j时所能得到的最大价值,可得状态转移方程s[i][j]=max{s[i-1][j-k]+h[i][k] (0<=k<=j)}。边界条件和普通01背包类似,就不再赘述了。现在想必大家最关心的问题是h[i][k]如何得到,其实这个并不困难,我们知道,如果将一棵树的根去掉,那么这个根原来的子树就会形成一座森林,这个性质给了我们启发,实际上要求一棵树的h[i][k]我们可以递归的求这棵树的每个子树的h[i][k](即把该树根的所有子树看成一座森林像上面一样按泛化物体的思想做01背包),因为子树对根的依赖关系,每个子树的h[i][k]求出以后可以很容易的求到当前树的h[i][k]。这样h[i][k]的问题就解决了,按照这种递归的思想就能比较容易写出代码了。关键要注意的地方就是在计算某个子树的h[i][k]时,根是必须选的,所以在动态规划的时候要为根留出空间,DP结束过后要将根的值加到结果中,我就是开始没注意这个问题导致最终走了N多弯路才AC。

[代码]

#include <iostream>

using namespace std;

struct TREENODE
{
      int nWeight;
      TREENODE *pChildren, *pBrothers;
};

void Solve(TREENODE *pNodes, int nCost, int nWeight[])
{
      int nCnt=0;
      TREENODE *NodesPt[300];

      for (TREENODE *p=pNodes->pChildren; p!=NULL; p=p->pBrothers) NodesPt[nCnt++]=p;
      for (int i=0; i<=nCost; i++) nWeight[i]=0;
      nCost=(pNodes->nWeight==0 ? nCost : nCost-1);
      for (int i=0; i<nCnt; i++)
      {
            int w[301];

            Solve(NodesPt[i], nCost, w);
            for (int j=nCost; j>=1; j--)
            {
                  for (int k=1; k<=j; k++) nWeight[j]=max(nWeight[j], nWeight[j-k]+w[k]);
            }
      }
      if (pNodes->nWeight==0) return;
      for (int i=nCost+1; i>=1; i--) nWeight[i]=nWeight[i-1]+pNodes->nWeight;
}

int main()
{
      TREENODE Nodes[301];

      memset(Nodes, 0, sizeof(Nodes));
     
      int n, m;
     
      cin>>n>>m;
      for (int i=1; i<=n; i++)
      {
            int nParent;

            cin>>nParent>>Nodes[i].nWeight;
            if (Nodes[nParent].pChildren==NULL) Nodes[nParent].pChildren=Nodes+i;
            else
            {
                  TREENODE *pNode=Nodes[nParent].pChildren;

                  while (pNode->pBrothers!=NULL) pNode=pNode->pBrothers;
                  pNode->pBrothers=Nodes+i;
            }
      }

      int nWeight[301];

      Solve(Nodes, m, nWeight);
      cout<<nWeight[m]<<endl;
      return 0;
}

 

通用有依赖的背包问题模板代码

struct TREENODE
{
      int nVolume, nWeight;
      TREENODE *pChildren, *pBrothers;
};

void Solve(TREENODE *pNodes, int nCost, int nWeight[])
{
      int nCnt=0;
      TREENODE *NodesPt[MAX_VOLUME];

      for (TREENODE *p=pNodes->pChildren; p!=NULL; p=p->pBrothers) NodesPt[nCnt++]=p;
      for (int i=0; i<=nCost; i++) nWeight[i]=0;
      nCost=(pNodes->nWeight==0 ? nCost : nCost-pNodes->nVolume);
      for (int i=0; i<nCnt; i++)
      {
            int w[MAX_VOLUME+1];

            Solve(NodesPt[i], nCost, w);
            for (int j=nCost; j>=NodesPt[i]->nVolume; j--)
            {
                  for (int k=NodesPt[i]->nVolume; k<=j; k++) nWeight[j]=max(nWeight[j], nWeight[j-k]+w[k]);
            }
      }
      if (pNodes->nWeight==0) return;
      for (int i=nCost+pNodes->nVolume; i>=pNodes->nVolume; i--) nWeight[i]=nWeight[i-pNodes->nVolume]+pNodes->nWeight;
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值