DP(动态规划)常见问题以及优化总结
Dynamic Progamming
区间DP
最长回文子串:
最长回文子序列:
硬币排成线:
\(dp_{1n}<0\)先手输
区间DP的优化
四边形不等式:
考虑一般的区间DP方程
定义1:
我们称定义在整数集上的二元函数\(w(i,j)\)满足单调性,如果对于任意整数\(i\le i' \le j \le j'\)都有\(w(i',j) \le w(i,j')\)
定义2:
我们称定义在整数集上的二元函数\(w(i,j)\)满足四边形不等式,如果对于任意整数\(i<i+1 \le j < j+1\),有:
不建议在考场上证明\(w(i,j)\)满足四边形不等式,可以考虑打表确定,因为证明过于繁琐
还需要注意,\(w(i,j)\)必须满足是关于\(i,j\)的二元函数,以石子合并为例吗,它的\(w(i,j)\)是\([i,j]\)内的石子堆数,即\(w(i,j)=sum_i-sum_{j-1}\),但是计算矩阵合并,合并\(i...j\)的权值与端点和合并次数有关,不能考虑单调性和四边形不等式
定理(四边形不等式定理)
如果\(w(i,j)\)满足四边形不等式,则使用动态规划(DP)计算的\(dp\)时间复杂度是\(O(N^2)\)
引理1
如果\(w(i,j)\)满足四边形不等式,则使用其计算的\(dp\)也满足四边形不等式
引理2
记\(s_{ij}\)是\(dp_{ij}\)取得最优值的断点,如果\(dp\)满足四边形不等式,则有\(s_{i,j-1}\le s_{ij}\le s_{i+1, j}\)
代码:
const int N = 1005;
int dp[N][N], s[N][N], w[N][N];
void solve() {
int n;
cin >> n;
// 初始化
for (int i = 1; i <= n; i++)
s[i][i] = i; // 初始最优决策点
for (int i = 1; i <= n; i++)
//给DP赋初值
// 预处理w数组(根据题目具体需求)
for (int i = 1; i <= n; i++) {
w[i][i] = dp[i][i];
for (int j = i + 1; j <= n; j++)
w[i][j] = w[i][j-1] + dp[j][j]; // 举例:区间和
}
// 区间DP + 四边形不等式优化
for (int len = 2; len <= n; len++) { // 枚举区间长度
for (int i = 1; i <= n - len + 1; i++) { // 枚举起点
int j = i + len - 1; // 终点
dp[i][j] = INT_MAX;
// 利用四边形不等式优化k的枚举范围
int l = s[i][j-1], r = s[i+1][j];
for (int k = l; k <= r && k < j; k++)
if (dp[i][j] > dp[i][k] + dp[k+1][j] + w[i][j]) {
dp[i][j] = dp[i][k] + dp[k+1][j] + w[i][j];
s[i][j] = k; // 更新最优决策点
}
}
}
cout << dp[1][n] << "\n";
}
背包类DP:
\(w_i\):重量,\(v_i\)价值,\(W\)最大重量,\(V\)最大价值,(\(dp_{ij}表示用i个物品装j的重量的最大价值\))
0/1背包:
\[V=\sum x_nv_n, x_n=0 \lor1, n = 1, 2, 3, 4....,求最大值 \]限制条件
\[\sum x_nw_n \le W \]状态转移方程:
\[dp_{ij}=\max(dp_{i-1,j},dp_{i-1,j-w_i}+v_i) \]内存优化
\[dp_{i\&1,j} = \max(dp_{(i-1)\&1,j},dp_{(i-1)\&1,j-w_i}+v_i) \]
函数背包:(拿0/1举例)
价值不再是定值,而是关于剩余容量的函数,不妨设为\(v_i(x)\)
\[dp_{w} = \max(dp_{w - x} + v_i(x)) \]
完全背包:
区别于0/1背包,每件物品有\(\infty\)种,限制条件同0/1背包
状态转移方程:
\[dp_{ij}= \max_{ kw_i \le W}(dp_{i-1,j-kw_i}+kv_i) \]复杂度优化:
for (int i = 0; i < N; i++) for (int j = weights[i]; j <= V; j++) dp[j] = max(dp[j], dp[j - weights[i]] + values[i]);
多重背包:
每件物品有\(s_i\)种
状态转移方程:
\[dp_{ij}=\max_{k\le min(s_i,\frac{W}{w_i})}(dp_{i-1,j-kw_i}+kv_i) \]时间复杂度:
\(O(NW\sum s_i)\)
考虑优化
多重背包的优化
二进制优化:\(O(NW\sum s_i) \implies O(NWlog_2(\sum s_i))\)
考虑将每种物品二进制拆分,即对于\(s_i\),取\(\sum _{2^i \le s_i}2^i\)
具体代码如下:
cin >> n >> W;
for (int i = 1; i <= n; i++) cin >> weight[i] >> value[i] >> num[i];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= num[i]; j *= 2) {
num[i] -= j;
new_v[++new_n] = j * value[i];
new_w[new_n] = j * weight[i];
}
if (num[i]) {
new_v[++new_n] = num[i] * value[i];
new_w[new_n] = num[i] * weight[i];
}
}
for (int i = 1; i <= new_n; i++)
for (int j = W; j >= new_w[i]; j--)
Dynamic_Programming[j] =
max(Dynamic_Programming[j], Dynamic_Programming[j - new_w[i]] + new_v[i]);
cout << Dynamic_Programming[W] << "\n";
单调队列优化:\(O(NW\sum s_i) \implies O(NW)\)
注意原始一维方程:
这里不妨令\(m=k, j = mk +r(r = j \% w_i)\)
令\(g(k')=temp_{k'w+r}-k'v\)(\(temp\)是上一次的状态)
则原问题转换为:在区间\([m-s,m]\)内求\(g(k')\)的最大值,或者说是维护
显而易见:单调队列维护滑动窗口内最大值
其余细节详见如下代码:
int multipleKnapsack(int N, int V, vector<int>& v, vector<int>& w, vector<int>& s) {
vector<int> dp(V + 1, 0);
deque<int> q; // 存储容量索引
for (int i = 0; i < N; i++) {
int vi = v[i], wi = w[i], si = s[i];
vector<int> temp = dp; // 保存上一状态
for (int r = 0; r < wi; r++) { // 枚举余数
q.clear();
for (int k = 0; r + k * wi <= V; k++) { // 同余类容量
int j = r + k * wi; // 当前容量
// 维护窗口大小不超过si
while (!q.empty() && (k - q.front()) > si) q.pop_front();
// 计算g值 = temp[j] - k*vi
// 用队首最大值更新dp[j]
if (!q.empty()) {
int best_k = q.front();
dp[j] = max(temp[j], temp[r + best_k * wi] + (k - best_k) * vi);
}
// 维护队列单调性
while (!q.empty()) {
int last_k = q.back();
// 比较g值:temp[j] - k*vi vs temp[last_j] - last_k*vi
if (temp[j] - k * vi >= temp[r + last_k * wi] - last_k * vi)
q.pop_back();
else
break;
}
q.push_back(k);
}
}
}
return dp[V];
}
多维背包:
背包的参数有多个维度
例题,0和1在计算机世界中, 由于资源限制, 我们一直想要追求的是产生最大的利益。
现在,假设你分别是 \(m\)个\(n\) 和 个 的统治者. 另一方面, 有一个只包含\(0\) 和\(1\) 的字符串构成的数组。
现在你的任务是找到可以由 \(m\) 个\(0\) 和 \(n\)个\(1\) 构成的字符串的最大个数. 每一个 \(1\) 和 \(0\) 均只能使用一次。
给出的 \(0\)和 \(1\) 的个数不会超过 \(100\)。
给出的字符串数组的大小不会超过 \(600\) 。
二维的\(0/1\)背包
\[dp_{kij}=\max(dp_{k-1,i,j},dp_{k-1,i - zero_{k},j-one_{k}}+1) \]可以优化
\[dp_{ij}=\max(dp_{ij},dp_{i-zero,j-one}) \]
双序列动态规划
处理两个序列的某种关系
转移方程的定义:\(dp_{ij}\)表示从第一个序列\(i\)个元素至第二个序列的\(j\)个元素的最优解
DP状态:
\(dp_{00}:\)空序列对空序列
\(dp_{i0}:\)第\(i\)个元素对空序列
\(dp_0j:\)同上
常见的转移方程:(假定字符串从\(1\)开始)
最长公共子序列(\(Longest\) \(Common\) \(Subsequence\))
\[dp_{i0}=dp_{0j}=0\\ dp_{ij}= \begin{cases} dp_{i-1,j-1}+1 | s_i=t_j \\ \max(dp_{i-1,j}, dp_{i,j-1}) \end{cases} \]
编辑距离(将一个字符串转成另一个字符串的最小步数)
\[dp_{ij} = \begin{cases} dp_{i-1,j-1} | s_i=t_j\\ \min(dp_{i-1,j}, dp_{i,j-1},dp_{i-1,j-1})+1,删除、插入、替换操作 \end{cases} \]
不同的子序列(在\(s\)中查找\(t\)的出现次数)
\[dp_{i0}=1\\ dp_{ij}= \begin{cases} dp_{i-1,j-1}+dp_{i-1,j} | s_i=t_j,两者都可以\\ dp_{i-1,j},只能不匹配 \end{cases} \]
字符串交错(\(Interleaving\) \(Problem\),给定\(s_1\), \(s_2\), \(s_3\),问\(s_1,s_2\)能否交错成\(s_3\))
\[dp_{i0}=dp_{i-1,0} \land (s_{1i}=s_{3i})\\ dp_{0j}=dp_{0, j-1}\land (s_{2i} = s_{3j})\\ dp_{ij} = [dp_{i-1,j} \land (s_{1i}=s_{3,i+j})] \lor [dp_{i,j-1}\land (s_{2j}=s_{3,i+j})] \]
概率/期望DP
概率/期望 DP 通常已知初始的状态,然后求解最终达到目标的概率/期望,因此概率 DP 需要顺序求解。
随机游走:
有\(p\)的概率向右移动,\(1-p\)的概率向左移动
\[dp_{i}=p \cdot dp_{i+1}+(1-p) \cdot dp_{i-1} \]
抛掷硬币:
有\(p\)的概率成功,\(1-p\)的概率失败,求\(k\)次抛掷正面的期望抛掷次数
\[dp_{i}=1+p \cdot dp_{i+1} +(1-p) \cdot dp_{0} \]
处理有后效性的动态规划的方法
我们写\(dp\)状态转移方程时,都会注意无后效性这个极其关键的点
但是如果题目只能推出来存在后效性的\(dp\)状态转移方程,那么我们可以考虑使用特殊方法解决问题
高斯消元法
高斯消元法(Gaussian Elimination)是一种用于求解线性方程组的经典算法,也可用于计算矩阵的秩、行列式或逆矩阵。以下是其核心要点和步骤:
通过初等行变换将线性方程组的增广矩阵化为行阶梯形(REF)或简化行阶梯形(RREF),从而逐步消元求解。
-
构造增广矩阵
将线性方程组写成矩阵形式 ( \(A\mathbf{x} = \mathbf{b}\) ),并合并为增广矩阵 (\(A | \mathbf{b}\)$)。 -
前向消元(Forward Elimination)
目标:将矩阵化为上三角矩阵(行阶梯形,REF)。-
从第1行开始,逐行处理。
-
对第 ( i ) 行:
a. 选主元:选择当前列中绝对值最大的元素作为主元(避免除零,提高数值稳定性)。
b. 消元:用主元行消去下方所有行的当前列元素,即对第 ( j ) 行(( j > i ))执行:\[\text{Row}_j \leftarrow \text{Row}_j - \left( \frac{a_{ji}}{a_{ii}} \right) \times \text{Row}_i \]
-
-
回代(Back Substitution)
从最后一行开始,反向求解变量:\[x_i = \frac{b_i - \sum_{k=i+1}^n a_{ik}x_k}{a_{ii}} \] -
可选:简化行阶梯形(RREF)
进一步消去非对角线元素,使主元为1,且每行主元是唯一非零元素。
- 主元选择:部分选主元(Partial Pivoting)或完全选主元(Full Pivoting)可提高数值稳定性。
- 奇异矩阵检测:若消元过程中出现全零行,则矩阵奇异(无解或有无穷多解)。
- 时间复杂度:对 ( n \times n ) 矩阵为 ( O(n^3) )。
- 解线性方程组:如电路分析、力学问题。
- 求矩阵的逆:对单位矩阵做相同的行变换。
- 计算行列式:上三角矩阵的对角线乘积即为行列式。
- 矩阵的秩:非零行的数量。
解方程组:
增广矩阵:
消元后(REF):
回代得解:( x = 2, y = 3, z = -1 )。
高斯消元的实现:
using Matrix = vector<vector<double>>;
using Vector = vector<double>;
// 高斯消元法求解 Ax = b(返回解向量 x)
Vector solveLinearSystem(Matrix A, Vector b) {
const int n = A.size();
if (n == 0 || A[0].size() != n || b.size() != n) {
throw invalid_argument("Invalid matrix or vector dimensions.");
}
// 构造增广矩阵 [A | b]
Matrix Ab(n, Vector(n + 1));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
Ab[i][j] = A[i][j];
}
Ab[i][n] = b[i];
}
// 前向消元(带部分选主元)
for (int i = 0; i < n; ++i) {
// 选主元:找到当前列中绝对值最大的行
int maxRow = i;
double maxVal = abs(Ab[i][i]);
for (int k = i + 1; k < n; ++k) {
if (abs(Ab[k][i]) > maxVal) {
maxVal = abs(Ab[k][i]);
maxRow = k;
}
}
// 交换当前行和主元行
if (maxRow != i) {
swap(Ab[i], Ab[maxRow]);
}
// 检查主元是否为0(矩阵奇异)
if (abs(Ab[i][i]) < 1e-10)
throw runtime_error("Matrix is singular or nearly singular.");
// 消去下方行的当前列
for (int k = i + 1; k < n; ++k) {
double factor = Ab[k][i] / Ab[i][i];
for (int j = i; j <= n; ++j)
Ab[k][j] -= factor * Ab[i][j];
}
}
// 回代求解
Vector x(n);
for (int i = n - 1; i >= 0; --i) {
x[i] = Ab[i][n];
for (int j = i + 1; j < n; ++j)
x[i] -= Ab[i][j] * x[j];
x[i] /= Ab[i][i];
}
return x;
}
// 计算矩阵的行列式
double computeDeterminant(Matrix A) {
const int n = A.size();
if (n == 0 || A[0].size() != n)
throw invalid_argument("Matrix must be square.");
double det = 1.0;
int sign = 1; // 记录行交换的次数(奇数次交换会改变行列式符号)
for (int i = 0; i < n; ++i) {
// 选主元
int maxRow = i;
for (int k = i + 1; k < n; ++k)
if (abs(A[k][i]) > abs(A[maxRow][i]))
maxRow = k;
// 交换行并更新符号
if (maxRow != i) {
swap(A[i], A[maxRow]);
sign *= -1;
}
// 主元为0时行列式为0
if (abs(A[i][i]) < 1e-10)
return 0.0;
det *= A[i][i];
// 消去下方行
for (int k = i + 1; k < n; ++k) {
double factor = A[k][i] / A[i][i];
for (int j = i; j < n; ++j)
A[k][j] -= factor * A[i][j];
}
}
return det * sign;
}
求解概率DP的过程就是将状态转移方程建模成线性方程
// 高斯消元法解线性方程组
vector<double> gauss(vector<vector<double>> a) {
int n = a.size();
for (int i = 0; i < n; ++i) {
// 寻找主元
int pivot = i;
for (int j = i; j < n; ++j)
if (abs(a[j][i]) > abs(a[pivot][i]))
pivot = j;
swap(a[i], a[pivot]);
// 无解或无穷多解
if (abs(a[i][i]) < EPS)
continue;
// 消元
for (int j = i + 1; j <= n; ++j)
a[i][j] /= a[i][i];
for (int j = 0; j < n; ++j)
if (i != j)
for (int k = i + 1; k <= n; ++k)
a[j][k] -= a[j][i] * a[i][k];
}
vector<double> res(n);
for (int i = 0; i < n; ++i) {
res[i] = a[i][n];
}
return res;
}
// 期望DP + 高斯消元法求解
double solveExpectation(int n, vector<vector<pair<int, double>>>& graph, int start, int end) {
// 建立方程: E[i] = 1 + sum(p_ij * E[j] for all edges i->j)
vector<vector<double>> equations(n, vector<double>(n + 1, 0));
for (int i = 0; i < n; ++i) {
equations[i][i] = 1;
if (i == end) {
// 终点的期望为0
equations[i][n] = 0;
continue;
}
equations[i][n] = 1; // 加上1步
for (auto& edge : graph[i]) {
int j = edge.first;
double p = edge.second;
equations[i][j] -= p; // E[i] += p * E[j]
}
}
vector<double> res = gauss(equations);
return res[start];
}
高斯-赛德尔迭代法
高斯-赛德尔(Gauss-Seidel)迭代法是一种用于求解线性方程组的迭代方法,是雅可比迭代法的一种改进版本。
基本思想
对于线性方程组 \(Ax = b\),高斯-赛德尔迭代法将系数矩阵A分解为:
\(A = L + D + U\)
其中:
- L是严格下三角矩阵
- D是对角矩阵
- U是严格上三角矩阵
迭代公式为:
\(x^{(k+1)} = (D + L)^{(-1)}(b - Ux^{(k)})\)
算法步骤
-
给定初始近似解\(x^{(0)}\)
-
对于\(k=0,1,2,...\)直到收敛:
- 对于\(i=1\)到\(n\):\[x_i^{(k+1)} = \frac{(b_i - Σ_{j=1}^{i-1} a_{ij}x_j^{(k+1)} - Σ_{j=i+1}^n a_{ij}x_j^{(k)})} {a_{ii}} \]
- 对于\(i=1\)到\(n\):
收敛条件
高斯-赛德尔迭代法收敛的充分条件包括:
- 矩阵\(A\)严格对角占优
- 矩阵\(A\)对称正定
概率论知识
概率论基础
-
基本概念
- 样本空间 (\(Ω\)):所有可能结果的集合。
- 事件:样本空间的子集。
- 概率公理(柯尔莫哥洛夫公理):
- 非负性:$ P(A) \geq 0 $
- 规范性:$ P(\Omega) = 1 $
- 可列可加性:互斥事件$ A_i $ 满足$ P\left(\bigcup_{i=1}^\infty A_i\right) = \sum_{i=1}^\infty P(A_i) $。
-
条件概率与独立性
- 条件概率:$ P(A|B) = \frac{P(A \cap B)}{P(B)} $
- 独立性:$ A $ 与$ B $ 独立当且仅当$ P(A \cap B) = P(A)P(B) $。
-
全概率公式与贝叶斯定理
- 全概率公式:若 $ {B_i} $ 是样本空间的划分,则$ P(A) = \sum_i P(A|B_i)P(B_i) $。
- 贝叶斯定理:$ P(B_j|A) = \frac{P(A|B_j)P(B_j)}{\sum_i P(A|B_i)P(B_i)} $。
随机变量及其分布
-
随机变量类型
- 离散型:取值可数(如二项分布)。
- 连续型:取值不可数(如正态分布)。
- 混合型:兼具离散和连续特性。
-
分布函数与概率函数
- 累积分布函数 (CDF):$ F(x) = P(X \leq x)$。
- 概率质量函数 (PMF)(离散型):$ p(x) = P(X = x) $。
- 概率密度函数 (PDF)(连续型):$ f(x) = \frac{d}{dx}F(x) $,满足 $ P(a \leq X \leq b) = \int_a^b f(x)dx $。
-
常见离散分布
分布名称 参数 PMF 期望与方差 应用场景 伯努利 \(p\) \(p^x(1-p)^{1-x}\) $ E[X]=p , Var(X)=p(1-p) $ 单次试验成功概率 二项 \(n, p\) \(\binom{n}{k}p^k(1-p)^{n-k}\) \(E[X]=np\), \(Var(X)=np(1-p)\) n次独立伯努利试验成功次数 泊松 \(\lambda\) \(\frac{e^{-\lambda}\lambda^k}{k!}\) \(E[X]=\lambda\), \(Var(X)=\lambda\) 稀有事件发生次数 几何 \(p\) \((1-p)^{k-1}p\) \(E[X]=\frac{1}{p} , Var(X)=\frac{1-p}{p^2}\) 首次成功所需试验次数 负二项 \(r,p\) \(\binom{k-1}{r-1}p^r(1-p)^{k-r}\) \(E[X]=\frac{r}{p} , Var(X)=\frac{r(1-p)}{p^2}\) 第r次成功所需试验次数 -
常见连续分布
分布名称 参数 PDF 期望与方差 应用场景 均匀 $ a, b $ $ \frac{1}{b-a} $ $ E[X]=\frac{a+b}{2} , Var(X)=\frac{(b-a)^2}{12} $ 区间内等概率事件 指数 $ \lambda $ $ \lambda e^{-\lambda x} $ $ E[X]=\frac{1}{\lambda} , Var(X)=\frac{1}{\lambda^2} $ 等待时间(无记忆性) 正态 $ \mu, \sigma $ \(\frac{1}{\sigma\sqrt{2\pi}}e^{-\frac{(x-\mu)^2}{2\sigma^2}}\) \(E[X]=\mu , Var(X)=\sigma^2\) 自然现象测量误差 伽马 \(\alpha, \beta\) \(\frac{\beta^\alpha x^{\alpha-1}e^{-\beta x}}{\Gamma(\alpha)}\) \(E[X]=\frac{\alpha}{\beta} , Var(X)=\frac{\alpha}{\beta^2}\) 多个指数变量之和 卡方 \(k\) \(\frac{x^{k/2-1}e^{-x/2}}{2^{k/2}\Gamma(k/2)}\) \(E[X]=k, Var(X)=2k\) 方差分析、假设检验
多维随机变量
-
联合分布与边缘分布
- 联合CDF:$ F_{X,Y}(x,y) = P(X \leq x, Y \leq y) $。
- 边缘分布:$ F_X(x) = \lim_{y \to \infty} F_{X,Y}(x,y) $。
-
条件分布与独立性
- 条件PDF:$ f_{Y|X}(y|x) = \frac{f_{X,Y}(x,y)}{f_X(x)} $。
- 独立性:$ f_{X,Y}(x,y) = f_X(x)f_Y(y) $。
-
协方差与相关系数
- 协方差:$ \text{Cov}(X,Y) = E[(X-E[X])(Y-E[Y])] $。
- 相关系数:$ \rho_{X,Y} = \frac{\text{Cov}(X,Y)}{\sigma_X \sigma_Y} \in [-1,1] $。
-
常见多维分布
- 多项分布:离散型,二项分布的推广。
- 二维正态分布:参数为均值向量和协方差矩阵。
数字特征与极限定理
-
期望与方差
- 期望:$ E[X] = \sum x p(x) $(离散)或 $ \int x f(x)dx $(连续)。
- 方差:$ Var(X) = E[(X-E[X])^2] $。
-
矩生成函数 (MGF)
- 定义:$ M_X(t) = E[e^{tX}] $。
- 性质:\(E[X^n] = M_X^{(n)}(0)\),唯一确定分布。
-
大数定律 (LLN)
- 弱大数定律:样本均值依概率收敛于期望($ \bar{X}_n \xrightarrow{P} \mu $)。
- 强大数定律:样本均值几乎必然收敛于期望($ \bar{X}_n \xrightarrow{a.s.} \mu $)。
-
中心极限定理 (CLT)
-
若 $ X_i $ 独立同分布,$ E[X_i] = \mu , Var(X_i) = \sigma^2 $,则:
\[\frac{\bar{X}_n - \mu}{\sigma/\sqrt{n}} \xrightarrow{d} N(0,1). \] -
应用:近似计算$ P(\bar{X}_n \leq a) \approx \Phi\left(\frac{a-\mu}{\sigma/\sqrt{n}}\right) $。
-
近似方法与特殊分布
-
泊松近似
- 当 $ n $ 大、$ p $ 小时,二项分布$ B(n,p) \approx \text{Poisson}(\lambda=np) $。
-
正态近似
- 二项分布:若 $ np \geq 5 $ 且 $ n(1-p) \geq 5 $,则 $B(n,p) \approx N(np, np(1-p)) $。
- 泊松分布:$ \lambda $ 大时,$ \text{Poisson}(\lambda) \approx N(\lambda, \lambda) $。
-
其他近似
- 伽马近似泊松:$ \text{Poisson}(\lambda) $ 的CDF可通过伽马分布计算。
- \(t\)分布近似正态:小样本时,\(t\)-分布用于均值估计(自由度$ n-1 $)。

浙公网安备 33010602011771号