动态规划优化技术 (Dynamic Programming Optimization)
在解决复杂的 DP 问题时,朴素的状态转移往往伴随着高昂的时间代价。DP 优化的核心在于发掘转移方程中的数学性质(如单调性、凸性、特殊代数结构),从而利用数据结构或数学变换实现时空复杂度的飞跃。
设 f[i] 的最优决策点为 pi=argminj<i{f[j]+w(j,i)}。
若满足 i1<i2⟹pi1≤pi2,则称该 DP 具有决策单调性。这是斜率优化与四边形不等式优化的理论基石。
决策单调性的优越性源于代价函数 w(i,j) 的特殊代数性质。
定义:若 ∀a≤b≤c≤d,满足 w(a,c)+w(b,d)≤w(a,d)+w(b,c),则称函数 w 满足四边形不等式。
定理:若 w 满足四边形不等式,则对于转移方程 f[i]=minj<i{f[j]+w(j,i)},其最优决策点 pi 满足 p1≤p2≤⋯≤pn。
证明:
设 pi=k,即对于所有 j<k,f[k]+w(k,i)≤f[j]+w(j,i)。
我们需要证明对于 i′>i,决策点 k 优于任何 j<k。
由四边形不等式(取 j<k<i<i′):
w(j,i)+w(k,i′)≤w(j,i′)+w(k,i)
整理得:
w(k,i′)−w(k,i)≤w(j,i′)−w(j,i)
由于 k 是 i 的最优决策点:
f[k]+w(k,i)≤f[j]+w(j,i)⟹f[k]−f[j]≤w(j,i)−w(k,i)
联立两式:
f[k]−f[j]≤w(j,i)−w(k,i)≤w(j,i′)−w(k,i′)
从而:
f[k]+w(k,i′)≤f[j]+w(j,i′)
这说明在 i′ 处,k 依然比任何 j<k 更优。故 pi′≥k=pi。证毕。
斜率优化本质上是将 DP 转移方程转化为平面几何中的直线截距极值问题。
考虑 f[i]=minj<i{f[j]+ai⋅bj+ci+dj}。将其变形为:
f[j]+dj=(−ai)⋅bj+(f[i]−ci)
令 Yj=f[j]+dj,Xj=bj,Ki=−ai,Bi=f[i]−ci。
则方程变为:Bi=Yj−KiXj。
我们的目标是找到一个点 (Xj,Yj),使得截距 Bi 最小。
- 单调队列 (X,K 均单调):维护凸包,每次取队头斜率最接近 Ki 的点。O(N)。
- 二分查找 (X 单调, K 不单调):在凸包上二分查找切点。O(NlogN)。
- 李超线段树 / CDQ 分治 (均不单调):维护动态直线集。O(NlogN)。
适用场景:f[i][j]=mink<j{f[i−1][k]+w(k,j)},且 w 满足决策单调性。
若 p[i][j]≤p[i][j+1],通过递归函数 solve(i, L, R, optL, optR):
- 计算 mid=(L+R)/2 的最优决策点 optMid∈[optL,optR]。
- 递归
solve(i, L, mid-1, optL, optMid)。
- 递归
solve(i, mid+1, R, optMid, optR)。
复杂度:O(K⋅NlogN)。
方程:f[i]=minj<i{f[j]+(s[i]−s[j]+i−j−1−L)2}
变形:令 Ai=s[i]+i,Bj=s[j]+j+1+L。
则 f[i]=f[j]+(Ai−Bj)2=f[j]+Bj2−2AiBj+Ai2。
整理为 Y=KX+B 形式:
f[j]+Bj2=(2Ai)⋅Bj+(f[i]−Ai2)
Check Solution (C++)
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 50005;
ll n, L, s[MAXN], f[MAXN];
int q[MAXN];
ll A(int i) { return s[i] + i; }
ll B(int i) { return s[i] + i + 1 + L; }
ll X(int i) { return B(i); }
ll Y(int i) { return f[i] + B(i) * B(i); }
double slope(int i, int j) {
return (double)(Y(j) - Y(i)) / (X(j) - X(i));
}
int main() {
cin >> n >> L;
for (int i = 1; i <= n; i++) {
cin >> s[i];
s[i] += s[i - 1];
}
int hh = 0, tt = 0;
q[0] = 0;
for (int i = 1; i <= n; i++) {
while (hh < tt && slope(q[hh], q[hh + 1]) <= 2 * A(i)) hh++;
int j = q[hh];
f[i] = f[j] + (A(i) - B(j)) * (A(i) - B(j));
while (hh < tt && slope(q[tt - 1], q[tt]) >= slope(q[tt], i)) tt--;
q[++tt] = i;
}
cout << f[n] << endl;
return 0;
}
使用分治优化将邮局问题的时间复杂度从 O(N2) 优化到 O(KNlogN)(当 K 较小时更优)。
Check Solution (Logic)
void solve(int k, int L, int R, int optL, int optR) {
if (L > R) return;
int mid = (L + R) >> 1, opt = optL;
f[k][mid] = INF;
for (int i = optL; i <= min(mid - 1, optR); i++) {
ll val = f[k-1][i] + w(i + 1, mid);
if (val < f[k][mid]) {
f[k][mid] = val;
opt = i;
}
}
solve(k, L, mid - 1, optL, opt);
solve(k, mid + 1, R, opt, optR);
}