1. 程式人生 > >P2014 選課

P2014 選課

選擇 cout 一個 OS sam urn 賦值 重復 i+1

題目描述

在大學裏每個學生,為了達到一定的學分,必須從很多課程裏選擇一些課程來學習,在課程裏有些課程必須在某些課程之前學習,如高等數學總是在其它課程之前學習。現在有N門功課,每門課有個學分,每門課有一門或沒有直接先修課(若課程a是課程b的先修課即只有學完了課程a,才能學習課程b)。一個學生要從這些課程裏選擇M門課程學習,問他能獲得的最大學分是多少?

輸入輸出格式

輸入格式:

第一行有兩個整數N,M用空格隔開。(1<=N<=300,1<=M<=300)

接下來的N行,第I+1行包含兩個整數ki和si, ki表示第I門課的直接先修課,si表示第I門課的學分。若ki=0表示沒有直接先修課(1<=ki<=N, 1<=si<=20)。

輸出格式:

只有一行,選M門課程的最大得分。

輸入輸出樣例

輸入樣例#1: 復制
7  4
2  2
0  1
0  4
2  1
7  1
7  6
2  2
輸出樣例#1: 復制
13

Solution:

  本題是一道樹上背包類$DP$問題。

  由於本題的圖是一個森林,所以我們加一個根節點$0$連向各個森林,形成一棵新樹。

  定義狀態$f[i][j]$表示在以$i$為根節點的子樹中選了$j$個節點的最大價值。

  則不難想到狀態轉移方程:$f[i][j]=max(f[i][j],f[i][j-k]+f[v][k])$其中$v$是$i$的子節點,表示在$i$的子節點中選了$j-k$個物品和在$v$子樹上選$k$個物品的最大價值。

  回溯時註意逆序賦值:$f[i][j]=f[i][j-1]+w[i]$表示在$i$子樹上選$j$個物品必須先選$i$節點,逆序是為了防止重復選$i$物品。

  最後輸出目標狀態$f[0][m]$就$OK$了。

代碼:

 1 #include<bits/stdc++.h>
 2 #define il inline
 3 #define ll long long
 4 #define Max(a,b) ((a)>(b)?(a):(b))
 5 #define Min(a,b) ((a)>(b)?(a):(b))
 6 #define For(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
 7
#define Bor(i,a,b) for(int (i)=(b);(i)>=(a);(i)--) 8 using namespace std; 9 const int N=305; 10 int n,m,f[N][N],h[N],cnt,to[N],net[N],rd[N],w[N]; 11 il void add(int u,int v){to[++cnt]=v,net[cnt]=h[u],h[u]=cnt,rd[v]++;} 12 il void dfs(int u){ 13 f[u][0]=0; 14 for(int i=h[u];i;i=net[i]){ 15 int v=to[i]; 16 dfs(v); 17 Bor(k,0,m) Bor(j,0,k)f[u][k]=Max(f[u][k],f[u][k-j]+f[v][j]); 18 } 19 if(u) Bor(j,1,m) f[u][j]=f[u][j-1]+w[u]; 20 } 21 int main(){ 22 ios::sync_with_stdio(0); 23 cin>>n>>m; 24 memset(f,-1,sizeof(f)); 25 int x; 26 For(i,1,n){cin>>x>>w[i];if(x)add(x,i);} 27 For(i,1,n)if(!rd[i])add(0,i); 28 dfs(0); 29 cout<<f[0][m]; 30 return 0; 31 }

P2014 選課