This commit is contained in:
e2hang
2025-09-15 22:16:09 +08:00
parent eea1a643fb
commit e9519e8558
19 changed files with 513 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
### 动态规划的分类
动态规划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 专题。如果你有特定问题或需要代码实现某个例子,请提供更多细节!