67 lines
2.2 KiB
Markdown
67 lines
2.2 KiB
Markdown
在 **0/1 背包问题**中,使用一维 DP 数组时**必须逆序遍历背包容量**,主要原因在于**避免同一物品被重复计算**。这与二维 DP 的实现方式有本质区别。下面通过公式和例子详细说明。
|
||
|
||
---
|
||
|
||
## 为什么一维 DP 必须逆序遍历?
|
||
|
||
### 状态定义
|
||
一维 DP 数组定义为:
|
||
$$
|
||
dp[j] = \text{背包容量为 } j \text{ 时的最大价值}
|
||
$$
|
||
|
||
### 状态转移方程
|
||
对于物品 $i$(重量 $w_i$,价值 $v_i$):
|
||
$$
|
||
dp[j] = \max(dp[j], dp[j-w_i] + v_i) \quad (j \geq w_i)
|
||
$$
|
||
|
||
### 关键问题
|
||
- **正序遍历**会导致 $dp[j-w_i]$ 在更新 $dp[j]$ 时**已被当前物品更新过**,相当于重复使用该物品。
|
||
- **逆序遍历**保证 $dp[j-w_i]$ 始终是**上一轮(未考虑当前物品)**的结果,符合 0/1 背包的“一次性”规则。
|
||
|
||
---
|
||
|
||
## 二维 DP 为何不需要逆序?
|
||
|
||
二维 DP 定义为:
|
||
$$
|
||
dp[i][j] = \text{前 } i \text{ 个物品,容量 } j \text{ 时的最大价值}
|
||
$$
|
||
状态转移:
|
||
$$
|
||
dp[i][j] = \max(dp[i-1][j], dp[i-1][j-w_i] + v_i)
|
||
$$
|
||
- 由于直接依赖 **上一行 $dp[i-1][\cdot]$**,不存在状态覆盖问题,正序/逆序均可。
|
||
|
||
---
|
||
|
||
## 例子演示
|
||
假设物品 $w=2$, $v=3$,背包容量 $C=5$。
|
||
|
||
错误的正序遍历($j=2 \to 5$)
|
||
|
||
1. $j=2$:
|
||
$dp[2] = \max(0, dp[0]+3) = 3$
|
||
$\Rightarrow dp = [0, 0, 3, 0, 0, 0]$
|
||
2. $j=4$:
|
||
$dp[4] = \max(0, dp[2]+3) = 6$
|
||
$\Rightarrow$ **错误**:物品被重复使用两次!
|
||
|
||
### 正确的逆序遍历($j=5 \to 2$)
|
||
1. $j=5$:
|
||
$dp[5] = \max(0, dp[3]+3) = 0$ ($dp[3]$ 未更新)
|
||
2. $j=2$:
|
||
$dp[2] = \max(0, dp[0]+3) = 3$
|
||
$\Rightarrow dp = [0, 0, 3, 3, 3, 0]$
|
||
**正确**:物品仅使用一次。
|
||
|
||
---
|
||
|
||
## 总结
|
||
| 维度 | 遍历顺序 | 原因 |
|
||
| ------ | -------- | ------------------------------------------------------------ |
|
||
| 一维DP | **逆序** | 防止 $dp[j-w_i]$ 被当前物品污染,确保每个物品只计算一次。 |
|
||
| 二维DP | 任意顺序 | 状态分层存储($dp[i][j]$ 只依赖 $dp[i-1][\cdot]$),无覆盖风险。 |
|
||
|
||
**核心思想**:一维 DP 的空间优化需要逆序来保证状态的**无后效性**。 |