有依赖背包是有多个主件, 每个主件有多个附件, 但是附件不会再有自己的附件.
而树上背包就是构成了连续依赖的背包, 他们的关系是一棵树(保证不会循环依赖, 每个物品只会依赖最多1个物品)(取了根节点才能取第二层的节点, 取了x节点才能取x的儿子节点, 取了从根节点的一条链才能取叶子节点).
对于这样的问题, 我们的核心思想是: 树上背包=分组背包+依赖背包.
我们按照从叶子到根的顺序处理, 保证处理到当前节点x, 其儿子节点的贡献已经计算完毕.
这样, 我们可以发现, 因为每种贡献对应i往下后代节点的一种取法, 所以i这个儿子的所有贡献我们只能取一个, 因此, 每个儿子节点都可以看为一个分组, 每个分组只能取一个物品(即i儿子的后代节点的某种取法), 这里就转换为了分组背包.
同时, 我们发现, 如果想要取到某个儿子节点, 其父节点必须取到. 那么对于每个节点x, 如果我们想得到其儿子节点的贡献, 那么我们首先要保证取了x这个点. 这里有点类似依赖背包的思想. 当x节点计算完毕时, x给其父节点的贡献一定是包含x本身的, 即贡献一定包括主件本身(而不是放弃主件选了x的一堆儿子(附件)).
回到这个题, 每个物品的花费是1, 价值是a[i], 其依赖构成一棵树, 问最多取k个物品, 总价值最大是多少.
(这个题还有
O
(
n
V
)
O(nV)
O(nV)的写法)
代码:
int fa[M];
int v[M];
int n, m;
int dp[250][250];
vector<int> lp;
vector<int> son[250];
void dfs(int now) {
for (int i = 1; i <= n; ++i)if (fa[i] == now)son[now].emplace_back(i), dfs(i);
lp.emplace_back(now);
}
void init() {
n = read(), m = read();
lp.clear();
Mem(fa, 0);
for (int i = 0; i <= n; ++i) son[i].clear();
fill(dp[0], dp[0] + 250 * 250, 0);
for (int i = 1; i <= n; ++i)fa[i] = read(), v[i] = read(),
dp[i][1] = v[i];
dp[0][1] = 0;
//设一个根节点为0, 这个节点的花费是1, 价值是0. 答案就是0节点花费为m+1
dfs(0);//拓扑序
for (auto x:lp)
for (auto i:son[x])
// 对于固定的x, 每个儿子i的m+1个贡献最多取1个,
// 也就是对应分组背包的一个组.
for (int j = m + 1; j >= 0; --j)
for (int k = 1; k <= m + 1 && j - k >= 0; ++k)
//因为有依赖背包选了主件才能选附件,
// 所以背包容量从放好主件(k=1)开始枚举
checkMax(dp[x][j], dp[x][k] + dp[i][j - k]);
write(dp[0][m + 1]), enter;
}