Files
Data-Structure/Algorithm/DP-DynamicProgramming/Classification.md
2025-09-15 22:16:09 +08:00

6.7 KiB
Raw Blame History

动态规划的分类

动态规划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。
      • 示例代码:
        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 专题。如果你有特定问题或需要代码实现某个例子,请提供更多细节!