题目描述
标签:有依赖的背包、树形
d
p
dp
dp
题意:给定
N
N
N门课程,每门课程有一定的学分
s
i
s_i
si,每门课程有一门或者没有先修课(比如
a
a
a是
b
b
b的先修课,必须先学完
a
a
a才能学
b
b
b),一个学生从这些课程中选择
M
M
M门课程进行学习,求获得的最大学分是多少?(
1
<
=
N
,
M
<
=
300
1<=N,M<=300
1<=N,M<=300)
解决方案
题解:树形依赖背包指的就是一类具有 树形依赖关系 的背包问题。当选一个物品的前提是选另一件物品,而这些依赖关系构成了一个树形关系。在容量有限的情况下,然后求最大的价值,这类问题我们就称之为树形依赖背包。
设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]为以
i
i
i作为根节点的子树,在选了
j
j
j节课的情况下能够获得的最大学分。
首先得初始化,对于每个节点
i
i
i来说,当能够选的课程数大于等于
1
1
1的时候,能够获得至少第
i
i
i门课程的学分,即
d
p
[
i
]
[
j
]
=
s
[
i
]
dp[i][j]=s[i]
dp[i][j]=s[i](
j
>
=
1
j>=1
j>=1)
对于不同的容量
j
j
j,获得的最大得分是在子树里选
k
k
k个与非该子树中选
j
−
k
j-k
j−k个的价值,即:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
]
[
j
]
,
d
p
[
i
]
[
j
−
k
]
+
d
p
[
i
s
o
n
]
[
k
]
)
dp[i][j]=max(dp[i][j],dp[i][j-k]+dp[i_{son}][k])
dp[i][j]=max(dp[i][j],dp[i][j−k]+dp[ison][k])
通过这样,对于每个子树的组合,我们可以保证只能至多选一个物品,且每个子树只搜一次。
由于本题的图是个森林,我们可以构造一个虚拟的根节点(节点 0 0 0)把每个树的根节点连到节点 0 0 0,转化成一颗树,跑上面的逻辑就可以了。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 305;
int dp[N][N];
vector<int> e[N];
int n, m, fa[N], v[N], w[N], sum = 0;
void dfs(int u) {
for (int j = v[u]; j <= m; j++) dp[u][j] = w[u];
for (int i = 0; i < e[u].size(); i++) {
int son = e[u][i];
dfs(son);
for (int j = m; j >= v[u]; j--)
for (int k = 0; k <= j - v[u]; k++)
dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[son][k]);
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> fa[i] >> w[i];
v[i] = 1;
if (fa[i] != 0) e[fa[i]].push_back(i);
else e[0].push_back(i);
}
dfs(0);
cout << dp[0][m];
return 0;
}