luogu2014选课--树上背包DP

题目链接

https://www.luogu.org/problemnew/show/P2014

分析

巧妙的树上背包,$f[now][p]$表示$now$节点选了$p$门课所得最大收益

将$now$节点的每个子节点$v$看成一组物品,一组物品有$size[v]$个物品,每个物品的体积为$1$,选了$j$个物品的价值为$f[v][j]$

于是这样写状态转移

1
2
3
4
5
for(ri i=m;i>=0;i--){//不懂为什么倒序循环的可以去看看我的背包DP题解
for(ri j=0;j<=i;j++){
f[now][i]=max(f[now][i],f[now][i-j]+f[v][j]);
}
}

当然不要忘记把取$now$节点的贡献加入$f$中

同时注意到一开始形态可能是个森林,按照套路搞个$n+1$的虚点就好了

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cctype>
#include <iostream>
#define ll long long
#define ri register int
using std::min;
using std::max;
template <class T>inline void read(T &x){
x=0;int ne=0;char c;
while(!isdigit(c=getchar()))ne=c=='-';
x=c-48;
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
x=ne?-x:x;return;
}
const int maxn=1305;
const int inf=0x7fffffff;
struct Edge{
int ne,to;
}edge[maxn<<1];
int h[maxn],num_edge=0;
inline void add_edge(int f,int to){
edge[++num_edge].ne=h[f];
edge[num_edge].to=to;
h[f]=num_edge;
}
int f[maxn][maxn],n,m;
int a[maxn];
void dfs(int now){
int v;
f[now][0]=0;
for(ri i=h[now];i;i=edge[i].ne){
v=edge[i].to;
dfs(v);
for(ri i=m;i>=0;i--){
for(ri j=0;j<=i;j++){
f[now][i]=max(f[now][i],f[now][i-j]+f[v][j]);
}
}
}
if(now!=n+1){
for(ri i=m;i>=1;i--){
f[now][i]=f[now][i-1]+a[now];
}
}
return ;
}
int main(){
int x,k,y;
read(n),read(m);
for(ri i=1;i<=n;i++){
read(k),read(x);
a[i]=x;
if(!k) add_edge(n+1,i);
else add_edge(k,i);
}
dfs(n+1);
printf("%d\n",f[n+1][m]);
return 0;
}