1. 程式人生 > >洛谷·選課

洛谷·選課

初見安~這裡是傳送門:洛谷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——