完全背包(一维数组版)

本文介绍了一个经典的计算机科学问题——无限背包问题。问题设定为给定多种物品,每种物品都有对应的重量和价值,且数量不限。目标是在不超过背包最大载重量的前提下,选择物品组合以达到价值最大化。文章给出了具体的输入输出格式、示例,并详细阐述了解决该问题的状态转移方程。

Description
设有n 种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为M,今从n 种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于M,而价值的和为最大。

Input
第一行:两个整数,M(背包容量,M<= 200)和N(物品数量,N<= 30); 第2..N+1 行:每行二个整数Wi,Ui,表示每个物品的重量和价值。

Output
仅一行,一个数,表示最大总价值。

Sample Input
12 4
2 1
3 3
4 5
7 9

Sample Output
15

说明
本问题的数学模型如下(状态转移方程),设 f(x)表示重量不超过x公斤的最大价值。
则 f(x)=max{f(x-w[i])+c[i]} 当x>=w[i] 1<=i<=n (可理解为如果更有价值的就替换,用二维数组也可)。
for i:=1 to m do //以重量分阶段
for j:=1 to n do //价值
begin
if i>=w[j] then t:=f[i-w[j]]+p[j];
if t>f[i] then f[i]:=t
end;

var
  i,j,n,m,t:longint;
  w,p,f:array[0..2000]of longint;
begin
  readln(m,n);
  for i:=1 to n do
    readln(w[i],p[i]);
  for i:=1 to m do
    for j:=1 to n do
    begin
      if i>=w[j] then t:=f[i-w[j]]+p[j];
      if t>f[i] then f[i]:=t;
    end;
  writeln(f[m]);
end.
<think>嗯,我现在需要了解C++中的01背包问题的一维数组解法。我之前学过动态规划,但可能对优化空间复杂度不太熟悉。首先,我得回忆一下01背包问题的基本概念。 01背包问题应该是给定一组物品,每个物品有重量和价值,在背包容量限制下选择物品使得总价值最大,且每个物品只能选一次。传统的二维动态规划方法是用dp[i][j]表示前i个物品在容量j时的最大价值。状态转移方程大概是dp[i][j] = max(dp[i-1][j], dp[i-1][j - weight[i]] + value[i])。这样需要O(n*capacity)的空间,当容量很大时可能会占用较多内存。 现在问题是优化到一维数组。用户想知道如何用一维数组实现。那怎么压缩空间呢?我记得可能需要改变遍历的顺序。比如,二维情况下,每个状态依赖于上一行的左边状态,所以如果一维的话,可能需要逆序遍历容量,这样在更新的时候不会覆盖掉之前需要用的数据。 比如,假设用一维数组dp[j],其中dp[j]表示容量为j时的最大价值。初始化时,所有dp[j]都是0。然后对于每个物品,从后往前遍历容量,从capacity到weight[i],这样在更新dp[j]的时候,用到的dp[j - weight[i]]还是上一轮的结果,也就是没有选当前物品的状态。这样就能保证每个物品只被选一次。 举个例子,比如当前处理第i个物品,重量是w,价值是v。在二维数组中,处理第i行的时候,每个dp[i][j]依赖于dp[i-1][j]和dp[i-1][j - w] + v。而一维的情况下,如果正序遍历,那么前面的j - w可能已经被更新过了,导致重复选物品,这不符合01背包的要求。所以必须逆序处理容量。 那具体的代码结构应该是怎样的呢?大概的步骤是: 1. 初始化一个一维数组dp,大小为capacity+1,初始值为0。 2. 遍历每个物品,对于每个物品i,遍历j从capacity到weight[i](包括weight[i]),因为如果j < weight[i],当前物品放不下,不需要更新。 3. 对于每个j,dp[j] = max(dp[j], dp[j - weight[i]] + value[i])。 4. 最终dp[capacity]就是最大价值。 那这个一维数组的时间复杂度应该还是O(n*capacity),但空间复杂度降到了O(capacity),这确实节省了很多空间,特别是当capacity很大时,比如上百万的时候,二维数组可能无法处理,而一维数组可能可以。 需要注意的问题可能有:遍历的顺序必须是逆序的,否则会导致完全背包问题的效果(即物品可以被选多次)。还有,初始化的时候dp[0]是0,其他可能也是0,或者根据问题要求是否恰好装满的情况可能有所不同。比如,如果要求恰好装满,可能需要将dp初始化为负无穷,只有dp[0]为0,这样只有在恰好装满的情况下才有解。 比如,当题目是恰好装满背包的最大价值时,初始化可能不同。但通常情况下,01背包问题可能允许不超过容量,这时候初始化为0即可。 举个例子,假设物品有重量数组weights和值数组values,容量为cap。那么代码大概是这样的: vector<int> dp(cap + 1, 0); for(int i = 0; i < n; ++i){ for(int j = cap; j >= weights[i]; --j){ dp[j] = max(dp[j], dp[j - weights[i]] + values[i]); } } return dp[cap]; 这样就能得到最大价值。那这应该就是正确的一维数组实现方式。 总结来说,01背包一维数组解法的关键点在于逆序遍历容量,确保每个物品只被考虑一次。这样可以节省空间,同时保持正确性。</think>在C++中,**01背包问题的一维数组解法**通过优化动态规划的空间复杂度,将原本的二维数组压缩为一维数组,从而更高效地解决问题。以下是核心要点和实现步骤: --- ### **核心思想** - **状态定义**:`dp[j]` 表示容量为 `j` 的背包能装下的最大价值。 - **逆序遍历**:为避免重复选取物品(保证每个物品仅选一次),容量需从大到小遍历。 - **状态转移**:`dp[j] = max(dp[j], dp[j - weight[i]] + value[i])`,决策是否选择当前物品。 --- ### **实现步骤** 1. **初始化数组** 创建一维数组 `dp`,大小为 `capacity + 1`,初始值全为0(若要求恰好装满背包,需初始化为负无穷,仅 `dp[0] = 0`)。 2. **遍历每个物品** 对每个物品的重量 `weight[i]` 和价值 `value[i]`,从背包容量最大值逆向更新到当前物品重量。 3. **更新状态** 通过比较“不选当前物品”和“选当前物品”的价值,更新 `dp[j]`。 ```cpp #include <vector> using namespace std; int knapsack01_1D(vector<int>& weights, vector<int>& values, int capacity) { vector<int> dp(capacity + 1, 0); int n = weights.size(); for (int i = 0; i < n; ++i) { for (int j = capacity; j >= weights[i]; --j) { dp[j] = max(dp[j], dp[j - weights[i]] + values[i]); } } return dp[capacity]; } ``` --- ### **关键点** - **逆序原因**:正序遍历会导致小容量状态被覆盖,从而允许重复选择物品(变为完全背包问题)。 - **空间优化**:时间复杂度仍为 O(n·capacity),但空间复杂度从 O(n·capacity) 降至 O(capacity)。 - **应用场景**:适用于物品数量多或背包容量较大的情况,如竞赛题目或实际工程中的资源分配问题。 --- ### **示例说明** 假设物品信息如下: - 重量:`weights = {2, 3, 4, 5}` - 价值:`values = {3, 4, 5, 6}` - 背包容量:`capacity = 8` 最终 `dp[8]` 的值为最大价值(此例为10,选择重量3和5的物品)。 通过一维数组解法,既能高效计算最大价值,又显著节省内存空间。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值