【动态规划】选课(树形)

问题

描述 Description

学校实行学分制。每门的必修课都有固定的学分,同时还必须获得相应的选修课程学分。学校开设了N(N<300)门的选修课程,每个学生可选课程的数量M是给定的。学生选修了这M门课并考核通过就能获得相应的学分。
在选修课程中,有些课程可以直接选修,有些课程需要一定的基础知识,必须在选了其它的一些课程的基础上才能选修。例如《Frontpage》必须在选修了《Windows操作基础》之后才能选修。我们称《Windows操作基础》是《Frontpage》的先修课。每门课的直接先修课最多只有一门。两门课也可能存在相同的先修课。每门课都有一个课号,依次为1,2,3,…。 例如:
::点击图片在新窗口中打开::
表中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

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

分析

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

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

粘个小代码

type cc=record
     l,r:longint;
end;
var
  tree:array[1..1000] of cc;
  a:array[1..1000,1..1000] of longint;
  m,i,x,y,z,t:longint;
begin
  readln(m);
  for i:=1 to m do
  begin
    readln(x,y,z);
    a[x,y]:=z;
    if tree[x].l=0 then tree[x].l:=y;
    t:=tree[x].l;
    while(tree[t].r<>0)do t:=tree[t].r;
    tree[t].r:=y;
  end;
end.

转化为二叉树之后,我们很容易就能写出方程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]}只和当前这门课选不选有关,这是很显然的,完全根据我们转换出的二叉树的定义和题目要求得来。

反思

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

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

code

program liukee;
type lkj=record
    l,r,num:longint;
end;

var
  f:array[0..1000,0..1000] of longint;
  tree:array[0..1000] of lkj;
  n,m:longint;
procedure init;//读入数据,多叉转二叉
var
  i,x,y,t:longint;
begin
  readln(n,m);
  for i:=1 to n do
  begin
    readln(x,tree[i].num);
 if tree[x].l=0 then
   tree[x].l:=i
 else
 begin
   t:=tree[x].l;
   while(tree[t].r<>0) do t:=tree[t].r;
   tree[t].r:=i;
 end;
  end;
end;

function find(i,m:longint):longint;//记忆化搜索
var
  j,temp1,temp2:longint;
begin
  if f[i,m]<>-1 then exit(f[i,m]);
  if(i=0)or(m=0)then
  begin
    f[i,m]:=0;
 exit(0);
  end;
  temp1:=find(tree[i].r,m);
  for j:=0 to m-1 do
  begin
 temp2:=find(tree[i].l,j)+find(tree[i].r,m-1-j)+tree[i].num;
    if temp2>temp1 then temp1:=temp2;
  end;
  f[i,m]:=temp1;
  exit(f[i,m]);
end;

begin
  fillchar(f,sizeof(f),$ff);
  init;
  writeln(find(tree[0].l,m));
end.

 

posted @ 2010-11-16 20:10  liukee  阅读(1356)  评论(0编辑  收藏  举报