1. 程式人生 > 其它 >[oiclass1454]選課:樹上揹包

[oiclass1454]選課:樹上揹包

題目

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

輸入

第一行有兩個整數 \(N,M\) 用空格隔開。(\(1\leq N \leq 300,1\leq M\leq 200\))
接下來的 \(N\) 行,第 \(i+1\) 行包含兩個整數 \(k_i\)

\(s_i\), \(k_i\) 表示第 \(i\) 門課的直接先修課,\(s_i\) 表示第 \(i\) 門課的學分。若 \(k_i=0\) 表示沒有直接先修課(\(1\leq k_i \leq N, 1\leq s_i \leq 20\))。

輸出

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

輸入樣例

7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2

輸出樣例

13

題解

經典題,傳統解法是將講多叉樹轉化成二叉樹,然後在二叉樹上進行樹形DP,現在使用樹上揹包來解會更簡單。
需要事先學習樹上揹包,理解分組揹包的求法,理解如何將問題轉化成樹上揹包問題。
本題有兩種定義方式。

1

定義\(f[i][j]\)

表示以i為根選擇j門課程的最大得分(包含i的結點),程式碼如下:

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
const int N=300+5;
vector<int> g[N];
int n,m,u,v,w,f[N][N],s[N];
void dfs(int u){
	for(int i=1;i<=m;i++)f[u][i]=s[u];
	for(int i=0;i<g[u].size();i++){
		int v=g[u][i];
		dfs(v);
		for(int j=m;j>=0;j--){
			for(int k=0;k<j;k++){
				f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]);
			}
		}
	}
}
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1,x;i<=n;i++){
		scanf("%d %d",&x,&s[i]);
		g[x].push_back(i);
	}
	m++;
	dfs(0);
	printf("%d",f[0][m]);
}
2

定義\(f[i][j]\)表示以i為根選擇j門課程的最大得分(不包含i的結點),程式碼如下:

點選檢視程式碼
#include<iostream>
#include<vector>
using namespace std;
vector<int> g[301];
int n,m,x,s[301],f[301][201];
void dfs(int u){
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        dfs(v);
        for(int j=m;j>=0;j--){
            for(int k=0;k<j;k++){
                f[u][j]=max(f[u][j],f[u][j-k-1]+f[v][k]+s[v]);
            }
        }
    }
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>x>>s[i];
        g[x].push_back(i);
    }
    dfs(0);
    cout<<f[0][m];
}