一、01 背包问题
- 01 背包问题描述:有 n 件物品,每件的重量为 w[i] ,价值为 c[i] 。现有一个容量为 V 的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每件物品都只有 1 件。
- 暴力解法的时间复杂度为,利用动态规划时间复杂度可降为。
- 令 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]。由于只有这两种策略,且要求获得最大价值,因此,此即为状态转移方程。边界为。因此可以写出代码如下。
-
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]);
- 可以知道时间复杂度和空间复杂度都是 ,其中时间复杂度已无法再优化,但是空间复杂度还可以再优化。
- 注意到状态转移方程中计算 dp[i][v] 时需要计算 dp[i-1][0]~dp[i-1][v-w[i]] 中的最大值,而计算 dp[i+1][v] 时又需要计算 dp[i][0]~dp[i][v-w[i]] 中的最大值,dp[i-1][] 部分的数据又完全用不到了,因此可以直接开一个一维数组 dp[v],枚举方向为 i 从 1 到 n,v 从 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]);
- 一般对能够划分阶段的问题,都可以尝试把阶段作为状态的一维,这可以使我们更方便地得到满足无后效性的状态。如果当前设计的状态不满足无后效性,那么不妨把状态升维,即增加一维或若干维来表示相应的信息,这样可能就能满足无后效性了。
二、完全背包问题
- 完全背包问题描述:有 n 件物品,每件的重量为 w[i] ,价值为 c[i] 。现有一个容量为 V 的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每种物品都有无穷件。
- 和 01 背包一样,完全背包问题的每种物品都有两种策略,但是也有不同点。对第 i 件物品来说:①不放第 i 件物品,那么 dp[i][v] =dp[i-1][v],这跟 01 背包是一样的;②放第 i 件物品,这里的处理和 01 背包有所不同。完全背包选择放第 i 件物品之后并不是转移到 dp[i-1][v-w[i],而是转移到 dp[i][v-w[i]],这是因为每种物品可以放任意件,放了第 i 件物品后还可以继续放第 i 件物品,直到第 v-w[i] 小于 0 为止。因此状态转移方程为:,边界为。
- 同样也可以改写成一维形式的状态转移方程:,边界为。写成一维形式之后和 01 背包完全相同,唯一的区别在于这里 v 的枚举顺序是正向枚举,而 01 背包的一维形式中 v 必须是逆向枚举。完全背包的一维形式代码如下。
-
for (int i = 1; i <= n; i++) for (int v = w[i]; v <= V; v++) dp[v] = max(dp[v], dp[v - w[i]] + c[i]);
三、参考文献
[1] 胡凡,曾磊.算法笔记[M].机械工业出版社,2016:465.