斜率优化DP
斜率优化DP
例题
朴素dp
设
状态转移方程为:
其中
该做法时间复杂度为
优化
为方便表示,令
我们将式子改写为如下形式:
考虑一次函数式子
则转移方程
我们把
如图,我们将这个斜率为
容易发现,可能让
具体地,设
我们维护一个指针
在插入一个点
代码实现
- 初始状态入队
- 每次使用一条和
相关的直线 去切维护的凸包,找到最优决策,更新 。 - 加入状态
。如果一个状态(即凸包上的一个点)在 加入后不再是凸包上的点,需要在 加入前将其剔除。
时间复杂度
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 5e4 + 10;
int n, L;
int c[N], s[N];
int dp[N], q[N], head = 1, tail = 0;
int b(int i) {return dp[i] - (s[i] + i - L - 1) * (s[i] + i - L - 1); } // 定义 b
int k(int i) {return 2 * (s[i] + i - L - 1); } // 定义 k
int y(int j) {return dp[j] + (s[j] + j) * (s[j] + j); } // 定义 y
int x(int j) {return s[j] + j; } // 定义 x
double slope(int i, int j) { // 定义 K(a, b)
return 1.0 * (y(i) - y(j)) / (x(i) - x(j));
}
signed main() {
scanf("%lld%lld", &n, &L);
for (int i = 1; i <= n; i++) {
scanf("%lld", &c[i]);
s[i] = s[i - 1] + c[i];
}
memset(dp, INF, sizeof(dp));
dp[0] = 0;
q[++tail] = 0;
for (int i = 1; i <= n; i++) {
while (head < tail && slope(q[head], q[head + 1]) < k(i)) { // 改点不可能是最优决策点,出队
head++;
}
dp[i] = y(q[head]) - k(i) * x(q[head]) + (s[i] + i - L - 1) * (s[i] + i - L - 1);
while (head < tail && slope(q[tail - 1], q[tail]) > slope(q[tail], i)) { // 若该点不再是凸壳上的点,则将其踢出队列
tail--;
}
q[++tail] = i;
}
printf("%lld\n", dp[n]);
return 0;
}
小结
具体的代码实现因题就题,比较抽象,要考虑是上凸包还是下凸包,事踢对头还是踢队尾之类的。注意上凸包斜率越来越小,下凸包斜率越来越大。
CDQ优化
一般用不上,但是放在这里。
对于有些问题,斜率并不是单调的。这时我们需要维护凸包上的每一个节点,然后每次用当前的直线去切这个凸包。
本题与「玩具装箱」问题唯一的区别是,玩具的价值可以为负。延续之前的思路,设
状态转移方程为:
其中
将方程做相同的变换:
然而这时有两个条件不成立了:
- 直线的斜率不再单调;
- 每次加入的决策点的横坐标不再单调。
仍然考虑凸壳的维护。
在寻找最优决策点,也就是用直线切凸壳的时候,我们将单调队列找队首改为:凸壳上二分。我们二分出斜率最接近直线斜率的那条凸壳边,就可以找到最优决策。
在加入决策点,也就是凸壳上加一个点的时候,我们有两种方法维护。
第一种方法是直接用平衡树维护凸壳。那么寻找决策点的二分操作就转化为在平衡树上二分,插入决策点就转化为在平衡树上插入一个结点,并删除若干个被踢出凸壳的点。此方法思路简洁但实现繁琐。
来说一种基于 CDQ 分治的做法。
设
- 我们先调用
算出 。然后我们对 这个区间内的决策点建凸包,然后用这个凸包去更新 。这时我们决策点集是固定的,不像之前那样边计算 DP 值边加入决策点,那么我们就可以把 的 先按照直线的斜率 排序,然后就可以使用单调队列来计算 DP 值了。 - 对于
中的每个点,如果它的最优决策的位置是在 这个区间,在这一步操作中他就会被更新成最优答案。当执行完这一步操作时,我们发现 中的所有点已经发挥了全部的作用,凸壳中他们存不存在已经不影响之后的答案更新。因此我们可以直接舍弃这个区间的决策点,并使用 解决右区间剩下的问题。
时间复杂度
小结
对比「玩具装箱」和「玩具装箱 改」,可以总结出以下两点:
- 二分/CDQ/平衡树等能够优化 DP 方程的计算,于一定程度上降低复杂度,但不能改变这个方程本身。
- DP 方程的性质会取决于数据的特征,但 DP 方程本身取决于题目中的数学模型。
一个点
的斜率为 。 ↩︎
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效