动态规划
旷日持久的战役。
动态规划原理
动态规划的原理包含最优子结构、无后效性以及子问题重叠。
下面是 AI 对动态规划原理举出的典型例子:
-
最优子结构
一个问题的最优解包含其子问题的最优解。也就是说,我们可以通过组合子问题的最优解来构造原问题的最优解。这是动态规划能够“分而治之”的基础。 -
无后效性(未来不会影响过去)
也称为“马尔可夫性质”。一旦某个阶段的状态确定,后续决策的演变就只与这个状态有关,而与之前如何到达这个状态的路径无关。这意味着未来的决策不会影响过去的状态。 -
子问题重叠
在求解过程中,不同的决策序列可能会反复遇到相同的子问题。动态规划通过存储这些子问题的解(通常用数组或表格),使得每个子问题只计算一次,从而大幅提升效率。
注:对子问题重叠的正确理解:不是将状态集合划分成可交集合,而是子问题可以用于后续的多次贡献计算。
例1:P1216 [IOI 1994 / USACO1.5] 数字三角形 Number Triangles
首先分析为什么一定要用动态规划。
- 贪心是错误的,因为贪心的眼光是短浅的,只适配于局部最优解能推出全局最优解的情况。这个手玩样例就能发现。

这个题满足无后效性、子问题重叠与最优子结构。所以可以进行 dp。
- dp 因素:行列。
记 \(dp[i][j]\) 表示考虑到第 \(i\) 行 \(j\) 列的走过路径的最大权值和。
写出转移式:
code:
#include<bits/stdc++.h>
#define ll long long
#define rep(i,l,r) for(int i=(l);i<=(r);++i)
using namespace std;
const int N=1010;
int n;
int dp[N][N],a[N][N];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
rep(i,1,n){
rep(j,1,i)cin>>a[i][j];
}
dp[1][1]=a[1][1];
rep(i,2,n){
rep(j,1,i){
if(j-1>=1){
dp[i][j]=max(dp[i][j],dp[i-1][j-1]+a[i][j]);
}
dp[i][j]=max(dp[i][j],dp[i-1][j]+a[i][j]);
}
}
int ans=0;
rep(i,1,n){
ans=max(ans,dp[n][i]);
}
cout<<ans<<'\n';
return 0;
}
例2:\(O(n^2)\) 求解 LIS
LIS:最长上升子序列。

- 首先,无后效性是好说明的。
- 最优子结构也很好说明。我想得到 \([1,i]\) 的最长上升子序列,必定依赖某一段前缀 \([1,j]\)(有可能为空)加上末尾的一个数的最优解来更新。
- 子问题重叠:考虑从一个 \(i\) 开始贡献给它后面的 \(j\)。它是能够多次进行贡献的。
所以考虑进行 dp。贪心是错的,因为我如果向后先找到一个最近的比当前数大的数,放到子序列末尾,很可能因为找到一个极大值而丢弃了后面更优的解。
- dp 依赖的维度:序列的下标和权值数组。
记 \(dp[i]\) 表示强制以 \(a[i]\) 结尾的最长上升子序列的长度。
有转移式
例3:二分优化 LIS
此处有经典 trick:把答案作为 dp 存储的维度。
- 额外记一个辅助数组 \(g\),其中 \(g[i]\) 表示长度为 \(i\) 的上升子序列末尾的可能最小值。可以看到,加粗部分就是要求的答案。
那么考察这个 \(g\) 的单调性,采取反证法。假设 \(g[i]>g[i+1]\),那么我只要把 \(i+1\) 子序列删去头部,即可使 \(g[i] = g[i+1]\),矛盾!所以说 \(g[i] \le g[i+1]\)。所以 \(g\) 单调不降。
- 那么每次二分 \(g\),根据最优子结构(动态规划原理)可知对于当前 \(i\),我们需要找到一个 \(g[j]\) 满足 \(g[j] < a[i]\),此时令 \(dp[i] \leftarrow j+1\)。同时更新辅助数组 \(g\),即 \(g[dp[i]]= \min \ (g[dp[i]],a[i])\)。
闫氏 DP 分析法
由 yxc 提出的,他用画图辅助 DP 的建模过程,收到网友的推崇。
详细阅读:https://www.cnblogs.com/IzayoiMiku/p/13635809.html。
摘录两句名言:
\(\textcolor{blue}{所有的 DP 问题,本质上都是有限集中的优化问题--yxc}\)
\(\textcolor{red}{所有的 DP 优化,都是对代码的恒等变形--yxc}\)
要点总结
- 确定状态集合。又称“化零为整”。
- 列出 DP 属性:如
max/min/count/sum。 - 划分 DP 阶段:又称“化整为零”,即把状态集合划分为若干个子问题状态,此处用到了动态规划原理中的子问题重叠。
- 列 DP 转移方程:DP 的核心。
- corner case(边界条件):要格外小心。
例4:\(O(n^2)\) 求最长公共子序列

最长公共子序列目前最优的解法是用 bitset,但这里仅介绍如何用 \(O(n^2)\) 求解最长公共子序列。
先来三步走。
- 子问题重叠。没有问题。
- 无后效性,可以对后面进行贡献,没有问题。
- 最优子结构,也没有问题。
综上,可以 DP。
再用 trick 3 闫氏 DP 分析法。
- 确定状态集合,\(dp[i][j]\) 表示 \(a[1...i]\) 和 \(b[1...j]\) 最长公共子序列的集合。
- 列出 DP 属性:Max。
- 划分 DP 阶段:

- 列状态转移方程:
动态规划 trick
- 把答案作为 dp 维度。应用:二分优化 LIS。
- 反证法证明单调性。应用:二分优化 LIS。
- 闫氏 DP 分析法。这种方法使用门槛非常低,有画图就可以。
参考资源
- B 站 董晓算法。
- ima AI。
- https://www.cnblogs.com/IzayoiMiku/p/13635809.html——闫氏 DP 分析法。

浙公网安备 33010602011771号