有依赖的背包问题

有依赖的背包问题

有 $N$ 个物品和一个容量是 $V$ 的背包。

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

如下图所示:

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

每件物品的编号是 $i$,体积是 $v_i$,价值是 $w_i$,依赖的父节点编号是 $p_i$。物品的下标范围是 $1 \dots N$。

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

输出最大价值。

输入格式

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

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

输出格式

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

数据范围

$1 \leq N,V \leq 100$
$1 \leq v_i,w_i \leq 100$

父节点编号范围:

  • 内部结点:$1 \leq p_i \leq N$;
  • 根节点 $p_i=−1$;

输入样例

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

输出样例:

11

 

解题思路

  如果要选择第$i$个物品,那么就一定要选择第$i$个物品的根节点。也就是说每个物品都存在依赖关系,每个物品的选择都依赖于其父节点。

  对于任意一棵树,如果要从这棵树中选择物品,那么这棵树的根节点是一定要选择的。由根节点的每一个子节点所构成的子树,都是相互独立的,因此我们会自然想到枚举各个子树,求每个子树在不同体积限制下选择物品价值的最大值。

  对于一棵树所选择的物品,我们可以根据各个子树来进行划分,假设根节点有$k$个子节点。我们可以把选择的物品根据所属的子树分成$k$个部分。而在每个子树内部还可以继续细分成$m+1$个部分($m$是限制的体积),分别是$0 \sim m$,表示从这棵子树中选择物品的体积最多是多少。

  因此问题变成了,一共有$k$棵子树,在每一棵子树中可以选择用多少体积,在这样的情况下求总体积不超过$m$的情况下的最大价值。可以发现这是一个分组背包问题。每一棵子树可以看成是一个分组,而组中的$m+1$个不同的体积可以看成是$m+1$种不同的物品,每个组都只能选择一种物品。  

  定义状态$f(u,i,j)$表示在以$u$为根的树中,从前$i$棵子树中选择体积不超过$j$且必选根节点$u$的所有方案的集合,属性是价值的最大值。状态转移方程为$$f(u,i,j) = \max_{1 \leq v \leq k} \left\{ \max_{0 \leq s \leq j-v_u} \left\{ f(u,i-1,j-s) + f \left(v,\text{size}(v),s \right) \right\} \right\}$$

  其中$v$表示根节点$u$的子节点,$\text{size}(v)$表示$v$的子节点的个数,$s$表示在以$v$为根的子树中选择的物品体积不超过$s$。

  AC代码如下,时间复杂度为$O(nm^2)$:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 110;
 5 
 6 int n, m;
 7 vector<int> g[N];
 8 int v[N], w[N];
 9 int f[N][N][N];
10 
11 void dfs(int u) {
12     for (int i = v[u]; i <= m; i++) {
13         f[u][0][i] = w[u];
14     }
15     for (int i = 1; i <= g[u].size(); i++) {
16         int t = g[u][i - 1];    // 子树根节点
17         dfs(t);
18         for (int j = v[u]; j <= m; j++) {
19             for (int k = 0; k <= j - v[u]; k++) {
20                 f[u][i][j] = max(f[u][i][j], f[u][i - 1][j - k] + f[t][g[t].size()][k]);
21             }
22         }
23     }
24 }
25 
26 int main() {
27     scanf("%d %d", &n, &m);
28     
29     int root;
30     for (int i = 1; i <= n; i++) {
31         int t;
32         scanf("%d %d %d", v + i, w + i, &t);
33         if (t == -1) root = i;
34         else g[t].push_back(i);
35     }
36     
37     dfs(root);
38     printf("%d", f[root][g[root].size()][m]);
39     
40     return 0;
41 }

  还可以优化掉中间的一维,AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 110;
 5 
 6 int n, m;
 7 int head[N], e[N], ne[N], idx;
 8 int v[N], w[N];
 9 int f[N][N];
10 
11 void add(int v, int w) {
12     e[idx] = w, ne[idx] = head[v], head[v] = idx++;
13 }
14 
15 void dfs(int u) {
16     for (int i = v[u]; i <= m; i++) {
17         f[u][i] = w[u];
18     }
19     for (int i = head[u]; i != -1; i = ne[i]) {
20         dfs(e[i]);
21         for (int j = m; j >= v[u]; j--) {
22             for (int k = 0; k <= j - v[u]; k++) {
23                 f[u][j] = max(f[u][j], f[u][j - k] + f[e[i]][k]);
24             }
25         }
26     }
27 }
28 
29 int main() {
30     scanf("%d %d", &n, &m);
31     
32     int root;
33     memset(head, -1, sizeof(head));
34     for (int i = 1; i <= n; i++) {
35         int t;
36         scanf("%d %d %d", v + i, w + i, &t);
37         if (t == -1) root = i;
38         else add(t, i);
39     }
40     
41     dfs(root);
42     printf("%d", f[root][m]);
43     
44     return 0;
45 }

 

参考资料

  AcWing 10. 有依赖的背包问题(算法提高课):https://www.acwing.com/video/386/

posted @ 2022-07-30 10:35  onlyblues  阅读(264)  评论(0编辑  收藏  举报
Web Analytics