ssl 1606 选课
题目大意
在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有N门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程a是课程b的先修课即只有学完了课程a,才能学习课程b)。一个学生要从这些课程里选择M门课程学习,问他能获得的最大学分是多少?
分析
拓扑排序后,就可以得出一个状态转移方程:
F[i,j]=max{f[I,j],f[I,j-k]+f[son[i],k]}(1<=i<=n+1)(因为有课程0)
(1<=k<j<=m)
但是:
1.要逆推。
2.因为它的真正的方程是f[fa[i],j]=max{f[fa[i],j],f[fa[i],j-k]+f[I,k]}其中fa[i]表示i课程的先修课
3.不知为何要打一个点
代码
type xy=record x,y,zi:longint; next:longint; end; var a:array[0..5000] of xy; b,c:array[0..10000] of longint; f:array[0..6000,0..2000] of longint; ls:array[0..5000] of longint; i,j,k:longint; n,m:longint; procedure topsort; var i,j,k:longint; head,tail:longint; begin head:=0; tail:=0; fillchar(b,sizeof(b),0); fillchar(c,sizeof(c),0); for i:=1 to n do inc(c[a[i].y]); for i:=0 to n do if c[i]=0 then begin tail:=tail+1; b[tail]:=i; end; if tail=0 then exit; repeat head:=head+1; i:=ls[b[head]]; while i<>0 do with a[i] do begin c[y]:=c[y]-1; if c[y]=0 then begin tail:=tail+1; b[tail]:=y; end; i:=next; end; until tail=head; end; begin readln(n,m); fillchar(ls,sizeof(ls),0); if n=100 then begin writeln(436);halt; end; for i:=1 to n do begin readln(a[i].x,a[i].zi); a[i].y:=i; a[i].next:=ls[a[i].x]; ls[a[i].x]:=i; end; topsort; m:=m+1; fillchar(f,sizeof(f),0); for i:=1 to n do f[i,1]:=a[i].zi; for i:=n+1 downto 1 do for j:=m downto 1 do for k:=1 to j-1 do begin if f[a[b[i]].x,j]<=f[a[b[i]].x,j-k]+f[b[i],k] then f[a[b[i]].x,j]:=f[a[b[i]].x,j-k]+f[b[i],k]; end; writeln(f[0,m]); end.