目录见右上
站内文章欢迎各位转载,但是请注明出处(毕竟码字很辛苦哈)。

主题

1378 选课

原题:https://codevs.cn/problem/1378/

题目描述 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


个人解法:

这的确是树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的时候,我们发现之前的f[0,l](0<=l<=k)已经有值了(也就是不等于0)。那么这个时候我们求出的f[0,k]事实上就累加了之前某一个f[0,l]的值。

那为什么在其他节点不会有这样的问题呢?因为在其他节点求状态最优的时候,f[0,l](0<=l<=m(m为最大代价))一直是0,所以不会影响答案。

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

这里困扰了我两天啊……再一次感谢学长。


代码如下:

uses math;//懒人调用的数学函数库


type

treenode=record

           l,r,data:longint;

          end;


var

tree:array[0..300] of treenode;

f:array[0..300,0..300] of longint;

cost:array[0..300] of longint;

n,m,a,b,c,i,ans:longint;


procedure ins(f,s:longint);//就是以f为父亲,以s为左儿子建立关系。

begin

  if tree[f].l=0 then tree[f].l:=s//如果左儿子是空的,那么直接建立就好

   else

  begin

   f:=tree[f].l;//如果f的左儿子不是空的,那么s就应该是tree[f].l的兄弟节点,也就是tree[f].l的右儿子。

   while tree[f].r<>0 do

    f:=tree[f].r;

   tree[f].r:=s;

  end;

end;


procedure dfs(i:longint);

var

  j,k:longint;

begin

  if tree[i].l<>0 then dfs(tree[i].l);

  if tree[i].r<>0 then dfs(tree[i].r);//回溯DP

  for j:=0 to m do

  begin

   f[i,j]:=f[tree[i].r,j];

   for k:=0 to j-cost[i] do

    f[i,j]:=max(f[i,j],f[tree[i].l,k]+f[tree[i].r,j-k-cost[i]]+tree[i].data);//动态转移方程

  end;

end;


begin

read(n,m);

for i:=1 to n do

begin

  read(a,b);

  ins(a,i);//建树

  tree[i].data:=b;

end;

for i:=1 to n do

  cost[i]:=1;


dfs(0);


writeln(f[tree[0].l,m]);//很重要!!!

end.


一个困扰了我两天的题终于过了。当然在学长的指点中又发现了查找错误的神奇方法。总之自明自己实力不足,才能谦虚地不断进步。

当然,如果各位还有什么质疑欢迎提出。如果还有什么问题也乐意帮助。

4月份就省选了。这样的水平该是怎样的自嘲,我说不清楚……

不过,DP路上,还是希望与你们共同进步。

更多可以见https://liaoy148.lofter.com/post/1da8a74e_c11c5fb

评论
©主题 —— Powered by LOFTER