动态规划-01背包问题
背包问题是一类经典的动态规划问题,它非常灵活,变化多样,需要仔细体会。本书只介绍两类最简单的背包问题:01背包问题和完全背包问题,而这两种背包中,又以01背包为重。
-
多阶段动态规划问题
有一类动态规划可解的问题,它可以描述成若干个有序的阶段,且每个阶段的状态只和上一个阶段的状态有关,一般把这类问题称为多阶段动态规划问题。01背包问题就是这样一个例子。
-
01背包问题
01背包问题是这样的:
有n件物品,每件物品的重量为
w[i]
,价值为c[i]
。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每种物品都只有1件。样例
5 8 3 5 1 2 2 // w[i] 4 5 2 1 3 // c[i]
令dp[i][v]
表示前i件物品(1 <= i <= n ;,0 <= v <= V)恰好装入容量为v的背包中所能获得的最大价值。怎么求解dp[i][v]
呢?
考虑对第i件物品的选择策略,有两种策略:
- 不放第i件物品,那么问题转化为前i-1件物品恰好装入容量为v的背包中所能获得的最大价值,也即
dp[i-1][v]
- 放第i件物品,那么问题转化为前i-1件物品恰好装入容量为v-w[i]的背包中所能获得的最大价值,也即
dp[i-1][v-w[i]]+c[i]
由于只有这两种策略,且要求获得最大价值,因此
上面这个就是状态转移方程。注意到dp[i][v]
只与之前的状态dp[i-1][]
有关,所以可以枚举i从1到n,v从0到V,通过边界dp[0][v] = 0 (0 <= v <= V)
(即前0件物品放入任何容量为v的背包中都只能获得价值0)就可以把整个dp数组递推出来。而由于dp[i][v]表示的恰好为
v的情况,所以需要枚举dp[n][v](0<=v<=V)
,取其最大值才是最后的结果。
for(int i = 1 ; i <= n ; i++){
for(int v = w[i] ; v <= V ; v++){
dp[i][v] = max(dp[i-1][v],dp[i-1][v-w[i]]+c[i])
}
}
这样修改对应到图中可以这样理解:v的枚举顺序变为从右往左,dp[i][v]
右边的部分为刚计算过的需要保存给下一行使用的数据,而dp[i][v]
左上角和右边的部分放在一个数组里,每计算出一个dp[i][v]
,就相当于把dp[i-1][v]
抹消,因为在后面的运算中dp[i-1][v]再也用不到了。
我们把这种技巧称为滚动数组。
代码如下:
for(int i = 1 ; i <= n ; i++){
for(int v = V ; v >= w[i] ; v--){// 逆序枚举v
dp[v] = max(dp[v],dp[v-w[i]]+c[i]);
}
}
这样01背包问题就可以用一维数组表示来解决了,空间复杂度为O(V)。
特别说明:如果是用二维数组存放,v的枚举是顺序还是逆序都无所谓;如果使用一维数组存放,则v的枚举必须逆序!
-
完整的求解01背包问题的代码如下:
#include <cstdio> #include <algorithm> using namespace std; const int maxn = 100; // 物品最大件数 const int maxv = 1000; // V的上限 int w[maxn],c[maxn],dp[maxv]; int main(){ int n,V; scanf("%d%d",&n,&V); for(int i = 0 ; i < n ; i++){ scanf("%d",&w[i]); } for(int i = 0 ; i < n ; i++){ scanf("%d",&c[i]); } // 边界 for(int v = 0 ; v <= V ; v++){ dp[v] = 0; } for(int i = 1 ; i <= n ; i++){ for(int v = V ; v >= w[i] ; v--){ // 状态转移方程 dp[v] = max(dp[v],dp[v-w[i]]+c[i]); } } // 寻找dp[0...V]中最大的即为答案 int max = 0; for(int v = 0 ; v <= V ; v++){ if(dp[v] > max){ max = dp[v]; } } printf("%d\n",max); return 0; }
-
输入样例数据
5 8 3 5 1 2 2 4 5 2 1 3
-
运行结果
- 动态规划是如何避免重复计算的问题在01背包中非常明显。在一开始暴力枚举每件物品放或者不放入背包时,其实忽略了一个特性:第i件物品放或者不放而产生的最大值是完全可以由前面i-1件物品的最大值来决定的,而暴力做法无视了这一点。