跳转至

0-1 背包

问题描述

\(n\) 个物品 和 一个容量为 \(W\) 的背包,每个物品有重量 \(w_i\) 和价值 \(v_i\) 两种属性,要求:选若干物品放入背包,使背包中物品的 总价值最大背包中物品的总重量不超过背包的容量

状态转移方程

状态定义

f[i][j] 表示只考虑前 i 个物品时,容量为 j 的背包所能达到的最大总价值

状态转移

决策:每件物品只有 不放 两种可能的决策

考虑物品 i

  1. 如果不放入 i ,背包容量不变,所能达到的总价值和 i-1 件物品相同;

  2. 如果放入 i ,则背包容量要减去 i 的体积 w[i] ,总价值在 i-1 件的基础上加上所放物品的价值 v[i]

综上可得 状态转移方程

f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i])

边界条件

背包问题的边界条件非常简单,dp 数组全部初始化为 0 就好了

不过要注意有一类变种:如果题目中附加了条件 “恰好装满背包” ,则初始化时除了 f[][0] 初始化为为 0 ,f[][1..V]均应设为 -INF

Note

几乎所有背包问题的初始化都是上面两种情况之一,所以在其他背包问题中不再考虑边界条件

规划方向

二维数组存储所有状态

先看 i :很明显应该从小到大枚举 i

再看 j :有 i 的存在,j 的枚举顺序随意

代码如下:

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

压缩至一维

我们还可以使用 滚动数组 技巧,丢掉 i ,将空间压缩至 一维

但要注意,此时 j 应逆序枚举,原因如下:

2

已经遍历的被更新到状态 i ,没有遍历的维持 i-1 状态,我们要的 f[j-w[i]]i-1 状态的,也就是说要保证更新 f[j] 的时候 f[j-w[i]] 还是 i-1 的状态的,所以要逆序枚举

从本质上来说,这是因为我们在在考虑第 i-1 件物品时只能做一次决策,做完这次决策就要立马考虑下一个物品 i ,否则就可以在同一个物品上做多次决策,而这是不允许的

代码如下:

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

小优化

不叫优化的优化...

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

看看真正的优化,在 W 较大的时候比较有用

对于最后一件物品 f[W] ,只要知道 f[W-w[n]] 即可;对于倒数第二件物品 f[W-w[n]] ,只要知道 f[W-w[n...n-1] 即可

依此类推,对于第 i 件物品,只要知道 f[W-w[n...i])] 即可,所以可以有如下操作:

for(int i=n;i>=1;i--)
    sum[i]+=w[i];
    // sum[i] 预处理成 i..n 物品的 w[i] 和
for(int i=1;i<=n;i++){
    bound=max(W-sum[i],w[i]);
    for(int j=W;j>=w[i];j--)
        f[j]=max(f[j],f[j-w[i]]+v[i]);
}

评论