100 lines
6.7 KiB
Markdown
100 lines
6.7 KiB
Markdown
### 动态规划的分类
|
||
|
||
动态规划(Dynamic Programming, DP)是一种算法设计范式,用于解决具有重叠子问题和最优子结构的问题。它通过将问题分解成更小的子问题,并存储子问题的解来避免重复计算,从而提高效率。DP 的核心是状态定义、状态转移方程和边界条件。
|
||
|
||
DP 可以根据问题的结构和状态表示方式进行分类。常见的分类包括线性 DP、区间 DP、树形 DP、状态压缩 DP、数位 DP、背包 DP(有时视为线性 DP 的子类)、概率 DP 和博弈 DP 等。下面我将详细介绍每个分类,包括定义、特点、适用场景,并给出典型例子。对于每个例子,我会简要描述问题、状态定义和转移方程。如果需要代码实现,我可以进一步提供(这里用 Python 示例)。
|
||
|
||
#### 1. **线性 DP**
|
||
- **定义与特点**:问题可以沿着一个线性序列(如数组、字符串)进行处理,通常从左到右或从小到大递推。状态通常是一维或二维数组,转移简单,时间复杂度一般为 O(n) 或 O(n^2)。
|
||
- **适用场景**:序列优化问题,如最长子序列、路径计数等。
|
||
- **例子**:
|
||
- **斐波那契数列**:计算第 n 项斐波那契数(F(n) = F(n-1) + F(n-2))。
|
||
- 状态:dp[i] 表示第 i 项的值。
|
||
- 转移:dp[i] = dp[i-1] + dp[i-2]。
|
||
- 边界:dp[0] = 0, dp[1] = 1。
|
||
- 示例代码:
|
||
```python
|
||
def fib(n):
|
||
if n <= 1:
|
||
return n
|
||
dp = [0] * (n + 1)
|
||
dp[1] = 1
|
||
for i in range(2, n + 1):
|
||
dp[i] = dp[i-1] + dp[i-2]
|
||
return dp[n]
|
||
# 示例:fib(5) = 5
|
||
```
|
||
- **最长递增子序列 (LIS)**:给定数组,求最长递增子序列的长度。
|
||
- 状态:dp[i] 表示以第 i 个元素结尾的最长递增子序列长度。
|
||
- 转移:dp[i] = max(dp[j] + 1) for j < i and nums[j] < nums[i]。
|
||
- 边界:dp[i] = 1。
|
||
|
||
#### 2. **区间 DP**
|
||
- **定义与特点**:问题涉及合并或处理区间(如数组的子区间),通常枚举区间的长度和起点,从小区间向大区间递推。状态是二维数组 dp[l][r],表示区间 [l, r] 的最优值。
|
||
- **适用场景**:区间合并、括号匹配等。
|
||
- **例子**:
|
||
- **矩阵链乘法**:给定 n 个矩阵的维度,求最小乘法次数。
|
||
- 状态:dp[i][j] 表示从第 i 到第 j 个矩阵的最小乘法次数。
|
||
- 转移:dp[i][j] = min(dp[i][k] + dp[k+1][j] + dims[i-1]*dims[k]*dims[j]) for k in [i, j-1]。
|
||
- 边界:dp[i][i] = 0。
|
||
- 示例:矩阵尺寸 [10, 30, 5, 60],最小乘法次数为 7500。
|
||
- **石子合并**:n 堆石子合并成一堆的最小成本(相邻堆合并成本为石子数和)。
|
||
- 状态:dp[i][j] 表示合并 [i, j] 堆的最小成本。
|
||
- 转移:类似矩阵链乘法。
|
||
|
||
#### 3. **树形 DP**
|
||
- **定义与特点**:在树结构上进行 DP,通常从叶子节点向上递推。状态定义在树节点上,可能包括子树的信息。常用 DFS 实现。
|
||
- **适用场景**:树上的路径、覆盖、独立集等问题。
|
||
- **例子**:
|
||
- **树上最大独立集**:在树中选节点,使无相邻节点被选,且节点权值和最大。
|
||
- 状态:dp[u][0] 表示不选 u 的最大值;dp[u][1] 表示选 u 的最大值。
|
||
- 转移:dp[u][0] = sum(max(dp[v][0], dp[v][1]) for v in children);dp[u][1] = weight[u] + sum(dp[v][0] for v in children)。
|
||
- 边界:叶子节点 dp[u][0] = 0, dp[u][1] = weight[u]。
|
||
- **树的最长路径**:求树中任意两节点的最长路径长度。
|
||
|
||
#### 4. **状态压缩 DP**
|
||
- **定义与特点**:当状态数量不多时,用二进制位表示集合状态(比特位 DP)。状态通常是 dp[mask] 或 dp[i][mask],mask 表示子集。
|
||
- **适用场景**:组合优化,如子集问题,状态数不超过 2^20。
|
||
- **例子**:
|
||
- **旅行商问题 (TSP)**:从起点访问所有城市并返回的最短路径。
|
||
- 状态:dp[mask][u] 表示访问过 mask 集合的城市,最后在 u 的最小距离。
|
||
- 转移:dp[mask][u] = min(dp[mask - {u}][v] + dist[v][u]) for v != u。
|
||
- 边界:dp[1<<start][start] = 0。
|
||
- **Hamilton 路径**:求图中访问所有节点一次的路径。
|
||
|
||
#### 5. **数位 DP**
|
||
- **定义与特点**:处理数字的每一位,从高位到低位递推。状态包括当前位置、是否紧界(是否等于上限)、其他属性(如和的模)。
|
||
- **适用场景**:统计 [1, n] 内满足条件的数字个数。
|
||
- **例子**:
|
||
- **统计 [1, n] 内不含 49 的数字个数**。
|
||
- 状态:dp[pos][tight][has49] 表示从高位到 pos 位,tight 是否等于 n 的前缀,has49 是否已出现 49。
|
||
- 转移:枚举当前位 d,更新下一位状态。
|
||
- 边界:从最高位开始。
|
||
|
||
#### 6. **背包 DP**
|
||
- **定义与特点**:经典优化问题,状态表示容量和物品选择。通常一维或二维数组,优化空间。
|
||
- **适用场景**:资源分配、组合优化。
|
||
- **例子**:
|
||
- **01 背包**:n 物品,容量 V,求最大价值(每个物品选或不选)。
|
||
- 状态:dp[i][v] 表示前 i 物品,容量 v 的最大价值。
|
||
- 转移:dp[i][v] = max(dp[i-1][v], dp[i-1][v - w[i]] + val[i]) if v >= w[i]。
|
||
- 优化:一维 dp[v] = max(dp[v], dp[v - w[i]] + val[i])(逆序)。
|
||
- **完全背包**:物品无限选,转移正序。
|
||
|
||
#### 7. **概率 DP**
|
||
- **定义与特点**:涉及概率计算,状态表示期望值或概率。转移基于概率加权。
|
||
- **适用场景**:随机过程、游戏期望。
|
||
- **例子**:
|
||
- **扔硬币期望**:扔 n 次硬币,求达到 k 个正面的期望扔次数。
|
||
- 状态:dp[i] 表示已有 i 个正面的期望剩余扔次数。
|
||
- 转移:dp[i] = 1 + 0.5 * dp[i+1] + 0.5 * dp[i](需解方程)。
|
||
|
||
#### 8. **博弈 DP**
|
||
- **定义与特点**:两人博弈,状态表示当前局面下的最优值(胜负或分数)。常用 min-max。
|
||
- **适用场景**:游戏、Nim 堆等。
|
||
- **例子**:
|
||
- **Nim 游戏**:多堆石子,两人轮流取,求先手是否必胜。
|
||
- 状态: Grundy 值(异或所有堆的 mex 值)。
|
||
- **取石子游戏**:一堆石子,每次取 1~m 个,最后取者胜。
|
||
|
||
这些分类不是严格互斥的,有些问题可以归入多个类别(如背包可视为线性 DP)。在实际问题中,选择分类取决于问题结构。建议从简单线性 DP 开始练习,如 LeetCode 的 DP 专题。如果你有特定问题或需要代码实现某个例子,请提供更多细节! |