【恐怖の算法】 斜率优化

【恐怖の算法】 斜率优化

斜率优化是一种用于优化动态规划(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\) 的线性函数,那么可以通过斜率优化的方法来减少不必要的状态转移,从而降低时间复杂度。

步骤

  1. 推导状态转移方程:首先需要根据问题的具体要求,推导出动态规划的状态转移方程。
  2. 将状态转移方程转化为斜率形式:将状态转移方程进行变形,使得它可以表示为直线的斜率形式。
  3. 维护一个凸包:使用单调队列来维护一个凸包,保证队列中的斜率是单调的。
  4. 状态转移:在每次状态转移时,从队列中选择最优的决策点进行转移。

示例问题

考虑一个经典的斜率优化问题:给定 \(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;
}

代码解释

  1. 斜率计算slope 函数用于计算两个点之间的斜率。
  2. 单调队列维护:使用 q 数组作为单调队列,headtail 分别表示队列的头和尾。
  3. 状态转移:在每次状态转移时,从队列中选择最优的决策点进行转移,并更新单调队列。

复杂度分析

  • 时间复杂度:由于每个点最多入队和出队一次,因此时间复杂度为 \(O(n)\)
  • 空间复杂度:主要使用了单调队列和 DP 数组,空间复杂度为 \(O(n)\)

通过斜率优化,我们将原本 \(O(n^2)\) 的时间复杂度优化到了 \(O(n)\),大大提高了算法的效率。

噩梦算法の终结~

posted @ 2025-03-01 19:59  半眠七点不想睡  阅读(83)  评论(0)    收藏  举报
/* 设置动态特效 */