01背包

1. 01背包

现有 \(n\) 个物品, 已知第 \(i\) 个物品的价值为 \(w_i\), 体积为 \(v_i\), 你有一个体积为 \(m\) 的背包, 问: 使用该背包能带走的物品的价值最多是多少

1.1 状态表示

设状态 \(f_{i, j}\) 表示对于前 \(i\) 个物品, 容量为 \(j\) 的背包所能装下的最大价值

1.2 状态转移方程

我们可以发现, 对于第 \(i\) 个物品, 我们有两种选择:

  1. 装入背包, 此时背包中所有物品的价值为 \(f_{i - 1, j - v_i} + w_i\)
  2. 不装入背包, 此时背包中所有物品的价值为 \(f_{i - 1, j}\)

对于第 \(i\) 个物品, 我们选择价值最大的那个选择: \(f_{i, j} = \max(f_{i - 1, j}, f_{i - 1, j - v_i} + w_i)\)

1.3 代码实现

1.3.1 没有使用滚动数组, 空间复杂度为 \(O(nm)\)

for (int i = 1; i <= n; ++ i)
  for (int j = 0; j <= m; ++ j)
    if (j < v[i]) f[i][j] = f[i - 1][j];
    else f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);

cout << f[n][m] << endl; // f[n][m] 表示体积为 m 的背包对于前 n 个物品, 所能装走的最大的价值

1.3.2 使用滚动数组, 空间复杂度为 \(O(m)\)

我们可以发现, 对于上一段代码

  1. \(j < v_i\) 时, \(f_{i, j}\) 直接继承 \(f_{i - 1, j}\) 的值
  2. \(j \geq v_i\) 时, \(f_{i, j}\) 取的是 \(f_{i - 1, j}\)\(f_{i - 1, j - v_i} + w_i\) 之间的最大值(都是第 \(i - 1\) 层)

故尝试去掉 \(f\) 的第一维

for (int i = 1; i <= n; ++ i)
  for (int j = v[i]; j <= m; ++ j)
    f[j] = max(f[j], f[j - v[i]] + w[i]);

然而此时, 我们发现: 每当使用 \(f_{j - v_i} + w_i\) 更新 \(f_j\) 时, 这个 \(f_{j - v_i} + w_i\) 已经不是 \(f_{i - 1, j - v_i} + w_i\), 而是 \(f_{i, j - v_i} + w_i\) 了, 故第二层循环需要逆序

for (int i = 1; i <= n; ++ i)
  for (int j = m; j >= v[i]; -- j)
    f[j] = max(f[j], f[j - v[i]] + w[i]);

cout << f[m] << endl; // f[m] 表示体积为 m 的背包对于前 n 个物品, 所能装走的最大的价值
posted @ 2021-10-30 12:07  小阁下  阅读(40)  评论(0)    收藏  举报