有依赖的背包问题(22-22)

105 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 22 天,点击查看活动详情

有依赖的背包问题

题目描述

有 NN 个物品和一个容量是 VV 的背包。

物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。

如下图所示:
QQ图片20181018170337.png

如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。

每件物品的编号是 ii,体积是 viv_i,价值是 wiw_i,依赖的父节点编号是 pip_i。物品的下标范围是 1N1…N

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

输入格式

第一行有两个整数 NNVV,用空格隔开,分别表示物品个数和背包容量。

接下来有 NN 行数据,每行数据表示一个物品。
第 ii 行有三个整数 viv_i, wiw_i, pip_i,用空格隔开,分别表示物品的体积、价值和依赖的物品编号。
如果 pi=1p_i=−1,表示根节点。 数据保证所有物品构成一棵树。

输出格式

输出一个整数,表示最大价值。

数据范围

1N,V1001≤N,V≤100 1vi,wi1001≤v_i,w_i≤100

父节点编号范围:

  • 内部结点:1piN1≤p_i≤N;
  • 根节点 pi=1p_i=−1;

输入样例

5 7
2 3 -1
2 2 1
3 5 1
4 7 2
3 6 2

输出样例:

11

题目分析

意如其题,这是一道有依赖的背包问题

对于一棵有根树求子树的某种特性,我们首先想到的便是用dfs由根向下遍历,但由于有最大体积的限制,使得我们在对子树赋权时无法赋予某一特定体积所对应的价值。

而我们若是依次遍历,复杂度是 2k2^k 复杂度,这是无法接受的。

由于总体积数值较小,便可以以体积作为背包,存子树所有可取体积的价值最大值。

具体实现思路见代码注释。

Accept代码 O(nm2)O(nm^2)

#include <bits/stdc++.h>

using namespace std;

const int N = 110;
int h[N], e[N], ne[N];
int v[N], w[N], f[N][N];    // f[i][j] 表示以 i 为根的情况下体积最大为 j 的最大价值
int n, m, root, idx;

void add(int x, int y)
{
    e[idx] = y, ne[idx] = h[x], h[x] = idx ++;
}

void dfs(int x)
{
    for (int i = v[x]; i <= m; i ++) f[x][i] = w[x];
    for (int i = h[x]; i != -1; i = ne[i])      // 枚举以 x 为根的相邻节点
    {
        int y = e[i];
        dfs(y);
        for (int j = m; j >= v[x]; j --)      // 当前根为视角枚举体积,不可换序
            for (int k = j - v[x]; k >= 0; k --)    // 以子节点为视角枚举子节点子树体积,可换序
                f[x][j] = max(f[x][j], f[x][j - k] + f[y][k]);                
    }
}

int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i ++)
    {
        int p;
        cin >> v[i] >> w[i] >> p;
        if (p != -1) add(p, i);
        else root = i;
    }
    dfs(root);
    cout << f[root][m];
    return 0;
}