题目描述
在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 N ( 1 ≤ N ≤ 300 ) N(1\leq N \leq 300) N(1≤N≤300) 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程 a 是课程 b 的先修课即只有学完了课程 a,才能学习课程 b)。一个学生要从这些课程里选择 M ( 1 ≤ M ≤ N ) M(1\leq M \leq N) M(1≤M≤N) 门课程学习,问他能获得的最大学分是多少?
思路
考虑构造虚根 0 0 0 ,将不用预选课的课程改为预选课为 0 0 0,好处是将原来的不联通的图变成一棵树。
记 d p i , j dp_{i,j} dpi,j 表示以 i i i 为根的子树取 j j j个点的最大收入,则有:
d p i , j = max ( d p i , j , d p s o n 1 , n u m 1 + d p s o n 2 , n u m 2 ⋯ + d p s o n p , n u m p ) dp_{i,j}=\max(dp_{i,j},dp_{son_1,num_1} + dp_{son_2,num_2}\dots+dp_{son_p,num_p}) dpi,j=max(dpi,j,dpson1,num1+dpson2,num2⋯+dpsonp,nump)
且有 s o n 1 … s o n p son_1\dots son_p son1…sonp 均被 i i i 所指向,且 ∑ a = 1 p n u m a = j \sum_{a=1}^{p}{num_a} = j ∑a=1pnuma=j
因此,考虑类似在树上做背包的方法。
对于每一个点,还是有:
还是记当前节点为 n o w now now,取其 i i i 个子节点(包括本身),有:
d p n o w , i = max ( d p n o w , i , max j = 0 i − 1 ( d p t o k , j + d p n o w , i − j ) ) dp_{now,i} = \max(dp_{now,i},\max_{j=0}^{i-1}{(dp_{to_k,j+dp_{now,i-j})})} dpnow,i=max(dpnow,i,j=0maxi−1(dptok,j+dpnow,i−j))
其中 t o k to_k tok 表示 i i i 的其中一个儿子。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
int head[1005],nex[2005],to[2005],cnt = 0;
int dp[1005][305];
void add(int x,int y) {
nex[++cnt] = head[x];
head[x] = cnt;
to[cnt] = y;
}
void dfs(int now,int fa) {
for(int k = head[now];k;k = nex[k]) {
dfs(to[k],now);
for(int i = m;i >= 1;i--) {
for(int j = 0;j < i;j++) {
dp[now][i] = max(dp[now][i],dp[now][i - j] + dp[to[k]][j]);
}
if(now == 0) dp[now][i] = max(dp[now][i],dp[to[k]][i]);
}
}
}
signed main() {
scanf("%lld %lld",&n,&m);
for(int i = 1;i <= n;i++ ) {
int to;
scanf("%lld %lld",&to,&dp[i][1]);
add(to,i);
}
dfs(0,0);
printf("%lld\n",dp[0][m]);
return 0;
}