动态规划

📚 动态规划(Dynamic Programming)学习笔记

欢迎来到我的算法学习笔记!动态规划 是算法面试和竞赛中的重中之重,也是许多复杂问题的核心解法。本篇将带你从零开始,建立对 DP 的直觉和系统性思维。

📌 目录(点击跳转)


什么是动态规划

动态规划(DP) 是一种通过把原问题分解为相对简单的子问题,并存储子问题的解以避免重复计算,从而高效求解最优化问题的方法。它的核心思想是 “记住你算过的,避免重复劳动”

与分治法不同,DP 的子问题往往不是独立的,而是相互重叠的(例如递归树中的相同节点)。DP 通过 表格(数组) 记录中间结果,将指数级复杂度降为多项式级。

核心思想:最优子结构与重叠子问题

  • 最优子结构:一个问题的最优解包含其子问题的最优解。即,我们可以通过组合子问题的最优解来构造原问题的最优解。
  • 重叠子问题:在递归求解过程中,相同的子问题会被反复计算。DP 利用记忆化(Memoization)或自底向上的表格法消除这些重复。

例如,斐波那契数列 F(n)=F(n-1)+F(n-2) 中,F(3) 在计算 F(5)F(4) 时被重复计算多次,DP 通过数组保存 F(3) 的值,下次直接使用。

DP 的适用场景

DP 通常用于解决最优化问题(求最大值、最小值、最少步数等)或计数问题(求方案数),并且具备以下两个特征:

  1. 问题可以分解为规模更小的相似子问题(最优子结构)。
  2. 子问题的解可以被多次复用(重叠子问题)。

典型的 DP 应用包括:

  • 背包问题(0-1背包、完全背包)
  • 字符串编辑(最长公共子序列、编辑距离)
  • 路径规划(网格最短路径、硬币找零)
  • 股票买卖、区间 DP、树形 DP 等。

DP 解题四步法

无论题目多么复杂,都可以遵循这四步来设计 DP 算法:

  1. 定义状态(状态表示)
    确定 dp[i]dp[i][j] 的含义。例如:dp[i] 表示前 i 个物品能获得的最大价值,或 dp[i][j] 表示字符串 A 的前 i 个字符与 B 的前 j 个字符的最长公共子序列长度。

  2. 推导状态转移方程
    分析当前状态与之前状态的关系,写出递推式。这是 DP 的核心,通常基于“最后一步”或“决策”来思考。

  3. 初始化(边界条件)
    确定最基础的状态值,如 dp[0] = 0dp[i][0] = 0。边界处理不当会导致结果错误。

  4. 确定计算顺序
    根据状态转移的依赖关系,决定循环方向(通常从小到大,也可能从大到小)。对于二维 DP,常见为双重循环。

此外,必要时进行空间优化(滚动数组)以降低内存消耗。

动态规划分类详解

🔹 线性 DP

核心思想:状态沿着一个方向(如一维数组或两个序列)线性递推,通常从 dp[i-1]dp[i-1][j] 等前驱状态转移而来。
典型模型:最长上升子序列(LIS)、最长公共子序列(LCS)、最大子段和、编辑距离。
优化方向:LIS 可结合二分查找优化到 O(n log n);LCS 在特定数据下可用 bitset 加速。
📖 详细文章 | 📝 题单


🔹 多维 DP

核心思想:状态需要两个及以上的维度来描述(如 dp[i][j][k]),常见于二维网格、三维背包或资源分配问题。
典型模型:二维网格路径计数(含障碍)、多维背包(多种费用)、双序列加限制条件。
优化方向:滚动数组降维(如背包问题的一维优化);若维度过多,考虑状态压缩。
📖 详细文章 | 📝 题单


🔹 数位 DP

核心思想:解决与数字的数位构成相关的问题,通常统计区间 [L, R] 内满足特定性质的数字个数。
典型模型:不含某数字、数字和满足条件、能被某数整除、数位单调性等。
状态定义dp[pos][state][limit][lead],其中 pos 为数位位置,state 为当前累积状态,limit 表示是否紧贴上界,lead 表示前导零。
实现技巧:记忆化搜索是主流,避免繁琐的循环判断。
📖 详细文章 | 📝 题单


🔹 树形 DP

核心思想:在树上进行动态规划,利用 DFS 回溯时从子节点收集信息,推算出父节点的最优值。
典型模型:树上最大独立集(选/不选当前节点)、树的直径、树形背包(依赖背包)、换根 DP(二次扫描)。
状态定义:通常 dp[u][0/1] 表示以 u 为根的子树,u 选或不选的最优值;树形背包则用 dp[u][j] 表示以 u 为根的子树内选择 j 个节点的最优值。
📖 详细文章 | 📝 题单


🔹 概率 DP

核心思想:处理随机过程的期望值或概率分布,通常从终止状态倒推,利用全期望公式或线性期望性质。
典型模型:掷骰子达到目标的期望步数、随机游走吸收概率、马尔可夫决策过程。
状态定义dp[i] 表示从状态 i 到达目标状态的期望(或概率),转移方程常为 dp[i] = sum(p[i][j] * dp[j]) + w[i]。若存在环,需列方程高斯消元。
重要公式:全期望公式 E[X] = E[ E[X|Y] ] 是推导核心。
📖 详细文章 | 📝 题单


🔹 状压 DP

核心思想:用二进制位表示集合状态,将状态压缩为一个整数,在 n ≤ 20 时暴力枚举所有子集状态。
典型模型:旅行商问题(TSP)、哈密顿路径、覆盖问题(如铺瓷砖)、二分图完美匹配计数。
状态定义dp[mask]dp[mask][i],其中 mask 表示已选元素的集合,i 表示当前所在节点。
优化技巧:枚举子集 for (sub = mask; sub; sub = (sub-1) & mask),预处理辅助数组。
📖 详细文章 | 📝 题单


🔹 区间 DP

核心思想:状态定义在区间 [i, j] 上,通过枚举分界点将大区间拆分为两个或多个子区间,合并子区间的解得到当前区间的解。
典型模型:石子合并(最小/最大得分)、矩阵链乘、括号匹配、回文分割(最少切割)。
状态定义dp[i][j] 表示区间 [i, j] 上的最优值。
计算顺序:按区间长度从小到大枚举,确保更短区间的值已计算。
📖 详细文章 | 📝 题单


🔹 数据结构优化

核心思想:利用数据结构(如线段树、树状数组、单调队列、平衡树)来加速状态转移,将原本 O(n²) 甚至 O(n³) 的 DP 优化到 O(n log n) 或 O(n)。
典型应用

  • 单调队列优化:用于滑动窗口类转移,如 dp[i] = max_{j∈[i-w, i-1]} (dp[j] + val[j])
  • 树状数组/线段树:用于维护前缀最值、二维偏序,如 LIS 的 O(n log n) 实现、带权 LIS。
  • 斜率优化 / 凸包优化:将形如 dp[i] = min_j (dp[j] + cost(j,i)) 的转移转化为直线交点维护。
    📖 详细文章 | 📝 题单

🔹 其他

涵盖内容:以上未归类的 DP 类型,如:

  • 插头 DP / 轮廓线 DP:处理网格连通性、铺砖问题,状态表示轮廓线上的插头状态。
  • 动态 DP(动态树分治):在树结构上支持点权修改,用矩阵乘法和线段树维护 DP 值。
  • 博弈 DP:结合 SG 函数或极小极大搜索的 DP。
  • 字符串 DP(如 AC 自动机 + DP):处理多模式串匹配计数。
    说明:这些类型相对小众,但在高级竞赛中会出现,可根据兴趣学习。
    📖 详细文章 | 📝 题单

posted @ 2026-08-27 18:54  thenrify  阅读(2)  评论(0)    收藏  举报