背包问题(一) 01背包
题目释义
有一个背包容量为 \(m\) 的背包,\(n\) 个物品。每个物品的重量为 \(w\),价值为 \(v\) 。
要求在选取物品总重量不大于背包容量的情况下,使得选取物品总价值最大。
每种物品仅可使用一次。
分析
首先,我们用 \(f[i][j]\) 表示前 \(i\) 个物品放入容量为 \(j\) 的背包的最大价值。
对于每个物品,我们只有两个选择,即放和不放。
-
若放入背包,则 \(f[i][j] = f[i-1][j-w[i] ]+v[i]\)。
-
若不放入背包,则 \(f[i][j]=f[i-1][j]\)。
-
但我们还要考虑,背包容量不足时也不能放入背包。
因此,状态转移方程为
\(f[i][j]=max(f[i-1][j],f[i-1][ j-w[i] ]+v[i]);\)
Code:
for(int i = 1;i <= n;i++)// 依次枚举每个物品;
for(int j = m;j >= 0;j--)// 枚举背包容量;
if( j >= w[i] )// 放得下;
f[i][j] = max(f[i-1][j],f[i-1][ j-w[i] ]+v[i]);
else// 放不下;
f[i][j] = f[i-1][j];
以上的代码时间复杂度和空间复杂度均为 \(O(nm)\),若题目中给出 \(n,m \leq\) \(10000\) ,它可能就会出现 MLE 的情况。
优化
我们发现,\(f[i][j]\) 只与 \(f[i-1][...]\) 有关,那我们可以优化掉数组的第一维,直接用 \(f[j]\)
来表示处理到当前物品时背包容量为 \(j\) 时的最大价值,
得出以下方程:
\(f[j]=max(f[j-1],f[j-w[i]]+v[i]);\)
注意: 为防止之前的值被覆盖,需要逆向枚举背包容量。
而原先未经优化的代码无需考虑背包容量的枚举顺序。
空间复杂度由 \(O(nm)\) 降至 \(O(m)\)。
Code
for(int i=1;i<=n;i++)
for(int j=m;j>=w[i];j--)// 逆向枚举;
f[j]=max(f[j],f[ j-w[i] ]+v[i]);