洛谷·選課
初見安~這裡是傳送門:洛谷P2014
題目描述
在大學裡每個學生,為了達到一定的學分,必須從很多課程裡選擇一些課程來學習,在課程裡有些課程必須在某些課程之前學習,如高等數學總是在其它課程之前學習。現在有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
題解
每門課程所對應的先修課僅一門,所以我們在理解樣例的時候也可以自然而然地畫出一顆樹狀結構來——每門課(存的標號)都只有唯一的先修課即父節點,沒有先修課的父節點為0。所以我們就有了個大致的思路——樹形結構。
與此同時——題目限定了選課的數量並要求學分最高,我們又可以聯想到揹包問題(之後發背問題講解)——動態規劃,所以這就是一道揹包類的樹形DP問題了。
那麼問題來了——例如樣例作圖後如下:(圖醜勿噴
如果我們按照題目要求的邏輯來,就應該先從0開始dp下去,因為要保證在7前先修2,在5前先修7;但是按照我們揹包問題的思路可以發現:如果這棵樹大一點兒,可能會存在部分先修課被不選,而選上了需要這們先修課的選修課,這是不符合題意的。所以我們依照樹形dp的套路——dfs到葉子結點後再層層推上來,轉化為最基礎的問題。如果倒著來——也就是從葉子結點x開始,dp[ x ]=grade(學分)[ x ]; 然後遞歸回到x的父節點y進行dp,就意味著dp[ y ]為以y為父節點在m的限制下的最大子樹和。至於前文我們捨去從0開始dp的思路的顧慮,因為我們選出其最大子樹和後一定會加上它本身的grade,能保證每一個子樹都是符合要求選課的,所以溯洄到根節點0時同樣符合題意。
大致方向已定,我們開始思考演算法——開一個二維的dp陣列,dp[ i ] [ j ]代表到第 i 門課程時選 j 門課的最大的學分,所以最後我們要輸出的就是dp[0] [m]。(是的就是揹包的套路了,所謂樹形dp就是從下往上,轉化為從葉子結點到根節點的過程。)
轉檯轉移方程為:
dp[y][t]=max(dp[y][t],dp[y][t-j]+dp[x][j]);//x為y的子結點。
下面是程式碼及詳解——
#include<bits/stdc++.h>
using namespace std;
int m,n,grade[500];
int dp[500][500],fa[500];
int ,maxn=0;
vector<int> v[500];
bool vis[500];
void dfs(int x)
{
vis[x]=1;//標記是否走過這個點
dp[x][0]=0;//初始化
for(int i=0;i<v[x].size();i++)
{
int y=v[x][i];
if(vis[y]) continue;
dfs(y);//遍歷到這一子樹的葉子結點
for(int t=m;t>=0;t--)//倒序避免重複疊加(揹包狀態壓縮常識)
{
for(int j=t;j>=0;j--)
{
if(t>=j)
dp[x][t]=max(dp[x][t],dp[x][t-j]+dp[y][j]);//狀態轉移
}
}
}
if(x!=0)//非假結點,是有學分的
{
for(int t=m;t>0;t--)
{
dp[x][t]=dp[x][t-1]+grade[x];
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int a,b;
cin>>a>>b;
grade[i]=b;
v[a].push_back(i);//存圖
v[i].push_back(a);
}
dfs(0);
cout<<dp[0][m]<<endl;
return 0;
}
迎評:)
——End——