Unbounded Knapsack

完全背包问题

1. 问题定义

给定两个数组:

  • volume[]:存储每个物品的体积(例如[0, 2, 3, 4]
  • values[]:存储每个对应物品的价值(例如[0, 3, 4, 5]
  • 一个容量为m的背包(例如10

目标:选择物品(每种物品可以无限次选取),使得它们的总体积不超过m,且总价值最大化

与0-1背包问题的核心区别:完全背包中每种物品可以重复选择,这一特性直接影响动态规划的实现方式。

2. 动态规划(DP)方法

完全背包问题同样可以用动态规划高效解决,与0-1背包相比,状态定义相同但状态转移方式不同,时间复杂度仍为 \(O(n \times m)\)

2.1 状态转移

dp[i][j] 表示使用前 i 个物品(每种可重复选取)和背包容量 j 可实现的最大价值

对于每个物品 i(从 1 到 n)和每个可能容量 j(从1到 m):

  1. 排除物品 idp[i][j] = dp[i - 1][j](价值与不使用物品i时相同)
  2. 包含物品 i :如果 volume[i] ≤ j(物品能放下),dp[i][j] = values[i] + dp[i][j - volume[i]](注意此处是dp[i][j - volume[i]],允许重复选取当前物品)

递推关系为:

if (volume[i] ≤ j)
    dp[i][j] = max(dp[i - 1][j], values[i] + dp[i][j - volume[i]])

if (volume[i] > j)
    dp[i][j] = dp[i - 1][j]

3. 示例演练

使用以下数据:

  • volume = [0, 2, 3, 4]
  • values = [0, 3, 4, 5]
  • m = 10

步骤1:初始化DP表

img

步骤2:计算过程解析

  • 处理物品1(体积2,价值3)时,由于可重复选取,容量每增加2就能多放一个,价值递增3
  • 处理物品2(体积3,价值4)时,例如容量5:max(之前的6, 容量2的3+4=7),更新为7
  • 最终容量10的最大价值为15(选择5个物品1:5×3=15)

步骤3:最终结果

dp[3][10] = 15 → 最大价值为15

4. 代码实现(C++)

4.1 二维数组版本

#include <iostream>

using namespace std;

const int N = 1010;

int n, m; // n是物品数量,m是背包容量
int v[N], w[N]; // v=价值, w=体积
int f[N][N]; // dp表

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) 
        cin >> v[i] >> w[i];
    
    // 填充dp表
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= m; j++) {
            // 不选第i个物品
            f[i][j] = f[i-1][j];
            // 选第i个物品(可重复选)
            if (j >= w[i])
                f[i][j] = max(f[i][j], f[i][j - w[i]] + v[i]);
        }
    }
                
    cout << f[n][m] << endl;
    return 0;
}

4.2 一维数组优化版本

利用滚动数组优化空间,关键区别是正向遍历容量

#include <iostream>
using namespace std;

const int N = 1010;

int n, m;
int f[N]; // 优化为一维数组

int main() {
    cin >> n >> m;

    for (int i = 1; i <= n; i++) {
        int v, w;
        cin >> v >> w;
        // 正向遍历容量(允许重复选取当前物品)
        for (int j = w; j <= m; j++)
            f[j] = max(f[j], f[j - w] + v);
    }

    cout << f[m] << endl;
    return 0;
}

5. 空间优化解析

核心原理

完全背包的一维优化与0-1背包的关键区别在于容量遍历方向:

  • 0-1背包采用逆向遍历j从m到w[i]),确保每个物品只被使用一次
  • 完全背包采用正向遍历j从w[i]到m),允许前面的更新结果被后面使用,实现物品的重复选取

空间复杂度对比

  • 二维版本:O(n × m)
  • 一维版本:O(m),极大节省内存空间,尤其适合处理大规模问题
posted @ 2025-09-27 19:14  wz150432  阅读(9)  评论(0)    收藏  举报