【恐怖の算法】 斜率优化
【恐怖の算法】 斜率优化
斜率优化是一种用于优化动态规划(DP)算法时间复杂度的技巧,通常可以将一些原本时间复杂度较高的 DP 问题优化到线性时间复杂度。
原理
在某些动态规划问题中,状态转移方程可能具有以下形式:
\(dp[i] = \min_{0\leq j < i}\{f(j)+g(i,j)\}\)
其中 \(f(j)\) 是只与 \(j\) 有关的函数,\(g(i,j)\) 是与 \(i\) 和 \(j\) 都有关的函数。如果 \(g(i,j)\) 可以表示为关于 \(i\) 和 \(j\) 的线性函数,那么可以通过斜率优化的方法来减少不必要的状态转移,从而降低时间复杂度。
步骤
- 推导状态转移方程:首先需要根据问题的具体要求,推导出动态规划的状态转移方程。
- 将状态转移方程转化为斜率形式:将状态转移方程进行变形,使得它可以表示为直线的斜率形式。
- 维护一个凸包:使用单调队列来维护一个凸包,保证队列中的斜率是单调的。
- 状态转移:在每次状态转移时,从队列中选择最优的决策点进行转移。
示例问题
考虑一个经典的斜率优化问题:给定 \(n\) 个点 \((x_i, y_i)\),要求将这些点分成若干段,每段的代价为该段内所有点的 \(y\) 坐标之和加上一个常数 \(C\),求最小的总代价。
设 \(dp[i]\) 表示将前 \(i\) 个点分成若干段的最小代价,状态转移方程为:
\(dp[i] = \min_{0\leq j < i}\{dp[j]+(\sum_{k = j + 1}^{i}y_k)^2 + C\}\)
令 \(sum[i]=\sum_{k = 1}^{i}y_k\),则状态转移方程可以改写为:
\(dp[i] = \min_{0\leq j < i}\{dp[j]+(sum[i]-sum[j])^2 + C\}\)
C++ 实现
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXN = 50005;
int n, C;
int y[MAXN], sum[MAXN], dp[MAXN];
int q[MAXN]; // 单调队列
// 计算斜率
double slope(int j, int k) {
return (double)(dp[j] + sum[j] * sum[j] - dp[k] - sum[k] * sum[k]) / (sum[j] - sum[k]);
}
int main() {
cin >> n >> C;
for (int i = 1; i <= n; i++) {
cin >> y[i];
sum[i] = sum[i - 1] + y[i];
}
int head = 0, tail = 0;
q[tail++] = 0;
for (int i = 1; i <= n; i++) {
// 维护单调队列,删除斜率不满足条件的点
while (head + 1 < tail && slope(q[head], q[head + 1]) <= 2 * sum[i]) {
head++;
}
int j = q[head];
dp[i] = dp[j] + (sum[i] - sum[j]) * (sum[i] - sum[j]) + C;
// 插入新的点,维护凸包
while (head + 1 < tail && slope(q[tail - 2], q[tail - 1]) >= slope(q[tail - 1], i)) {
tail--;
}
q[tail++] = i;
}
cout << dp[n] << endl;
return 0;
}
代码解释
- 斜率计算:
slope函数用于计算两个点之间的斜率。 - 单调队列维护:使用
q数组作为单调队列,head和tail分别表示队列的头和尾。 - 状态转移:在每次状态转移时,从队列中选择最优的决策点进行转移,并更新单调队列。
复杂度分析
- 时间复杂度:由于每个点最多入队和出队一次,因此时间复杂度为 \(O(n)\)。
- 空间复杂度:主要使用了单调队列和 DP 数组,空间复杂度为 \(O(n)\)。
通过斜率优化,我们将原本 \(O(n^2)\) 的时间复杂度优化到了 \(O(n)\),大大提高了算法的效率。
噩梦算法の终结~

【恐怖の算法】 斜率优化
浙公网安备 33010602011771号