01背包问题/完全背包问题 滚动数组代码模板

博客介绍了两种背包问题。01背包问题中,有n件物品,每件有对应重量和价值,背包容量为V,每种物品只有1件,需选取物品使背包总价值最大;完全背包问题类似,但每种物品有无穷件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

01背包问题:
有 n 件物品, 每件物品的重量为 w[i], 价值为 c[i]。现有一个容量为 V 的背包, 问如何选取物品放入背包, 使得背包内物品的总价值最大。 其中每种物品都只有1件

for(int i=0;i<n;i++){
    for(int v=V;v>=w[i];v--){//逆向枚举
        dp[v]=min(dp[v-w[i]]+c[i],dp[v]);
    }
}

完全背包问题:
有 n 件物品, 每件物品的重量为 w[i], 价值为 c[i]。现有一个容量为 V 的背包, 问如何选取物品放入背包, 使得背包内物品的总价值最大。 其中每种物品都有无穷件

for(int i=0;i<n;i++){
    for(int v=w[i];v<=V;v++){
        dp[v]=min(dp[v-w[i]]+c[i],dp[v]);//正向枚举
    }
}
<think>我们讨论的是完全背包问题,其特点是每种物品有无限多个。与01背包问题(每种物品只能选一次)不同,在完全背包问题中,我们可以在不超过背包容量的前提下,无限次选取每种物品。 动态规划解法: 状态定义:dp[j] 表示容量为 j 的背包能够装下的最大价值。 状态转移:对于每种物品 i,体积为 v_i,价值为 w_i,我们考虑在背包容量允许的情况下,可以多次选取该物品。因此,在更新 dp[j] 时,我们使用正序遍历(与01背包的逆序相反),这样可以在更新当前状态时利用当前物品已经更新过的状态(即允许重复选取)。 状态转移方程: dp[j] = max(dp[j], dp[j - v_i] + w_i) 当 j >= v_i 模板代码(空间优化版): for i in range(n): # 遍历物品 for j in range(v_i, V+1): # 从小到大遍历背包容量 dp[j] = max(dp[j], dp[j - v_i] + w_i) 注意:这里内层循环是正序的,这样在更新dp[j]时,dp[j - v_i]可能已经更新过(即可能已经包含当前物品),因此可以实现重复选取。 下面我们给出一个完整的C++模板代码示例:</think># 完全背包问题动态规划实现模板 完全背包问题是一种经典的动态规划问题,其特点是**每种物品可以无限次使用**。与01背包问题(每种物品仅能使用一次)不同,完全背包问题需要调整状态转移策略来适应物品的无限次使用特性。 ## 核心算法思想 1. **问题定义**: - 给定背包容量 $V$ 和 $n$ 种物品 - 每种物品 $i$ 有体积 $v_i$ 和价值 $w_i$ - 目标:在不超过背包容量的前提下,最大化物品总价值 2. **状态定义**: - $dp[j]$:容量为 $j$ 的背包所能达到的最大价值 3. **状态转移方程**: $$ dp[j] = \max(dp[j], dp[j - v_i] + w_i) \quad \text{for } j = v_i \text{ to } V $$ 4. **关键区别**: - 01背包:内层循环**逆序**遍历(防止重复选取) - 完全背包:内层循环**顺序**遍历(允许重复选取) ## 空间优化实现模板(一维数组) ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int completeKnapsack(int V, vector<int>& v, vector<int>& w) { vector<int> dp(V + 1, 0); // 初始化dp数组 // 遍历所有物品 for (int i = 0; i < v.size(); i++) { // 顺序遍历背包容量(关键区别) for (int j = v[i]; j <= V; j++) { dp[j] = max(dp[j], dp[j - v[i]] + w[i]); } } return dp[V]; } int main() { // 示例数据 int V = 10; // 背包容量 vector<int> v = {2, 3, 4, 5}; // 物品体积 vector<int> w = {3, 4, 5, 6}; // 物品价值 int maxValue = completeKnapsack(V, v, w); cout << "最大价值: " << maxValue << endl; return 0; } ``` ## 二维数组实现模板(更易理解) ```cpp int completeKnapsack2D(int V, vector<int>& v, vector<int>& w) { int n = v.size(); vector<vector<int>> dp(n + 1, vector<int>(V + 1, 0)); for (int i = 1; i <= n; i++) { for (int j = 0; j <= V; j++) { // 不选当前物品 dp[i][j] = dp[i - 1][j]; // 选当前物品(可重复选) if (j >= v[i - 1]) { dp[i][j] = max(dp[i][j], dp[i][j - v[i - 1]] + w[i - 1]); } } } return dp[n][V]; } ``` ## 算法分析 1. **时间复杂度**: - $O(n \times V)$,其中 $n$ 是物品种类数,$V$ 是背包容量 2. **空间复杂度**: - 一维数组:$O(V)$ - 二维数组:$O(n \times V)$ 3. **正确性证明**: - 顺序遍历确保每个物品可被多次选择 - 状态转移方程保证最优子结构性质 ## 相关问题 1. **基础应用**: - 如何修改代码记录选择的物品组合? (添加选择路径追踪数组) - 当背包必须装满时,如何调整初始化? ($dp[0]=0$,其余初始化为负无穷) 2. **变种问题**: - 完全背包的最小物品数问题:求填满背包所需最少物品数量 (将 $\max$ 改为 $\min$,$w_i$ 设为1) - 组合问题:求填满背包的方案数 (将 $\max$ 改为加法,初始 $dp[0]=1$) 3. **性能优化**: - 当 $V$ 很大时如何优化? (使用滚动数组或数学优化) - 如何利用物品体积的整除关系优化? (预处理体积关系,减少状态数) 4. **实际应用场景**: - 硬币找零问题:给定面额组合求找零方案 (完全背包的典型应用) - 资源分配问题:在预算限制下最大化收益 5. **算法对比**: - 完全背包与01背包的核心区别是什么? (物品选取次数限制导致遍历顺序不同) - 多重背包问题如何转化为完全背包? (当物品数量 $c_i \times v_i > V$ 时)[^1] [^3]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值