树上背包入门 HDU 1561 O(n*V^2)的做法

有依赖背包是有多个主件, 每个主件有多个附件, 但是附件不会再有自己的附件.
而树上背包就是构成了连续依赖的背包, 他们的关系是一棵树(保证不会循环依赖, 每个物品只会依赖最多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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值