斜率优化DP学习笔记

一、简述斜率优化

初步了解

本文以luoguP3195 玩具装箱为例,我们很容易可以的出下面这个柿子:

[f_i = min_{j = 1}^{i - 1} { f_j + (i - j - 1 + s_i - s_j - L) ^ 2 } ]

(b_i = s_i + i)(j)(f_i) 的最优决策点,则有:

[f_i = f_j + (b_i - b_j - (L + 1)) ^ 2 ]

把只与 (j) 有关的放在左边:

[f_j + b_j + 2 b_j (L + 1) = 2 b_i b_j - b_i ^ 2 - (L + 1) ^ 2 + 2 b_i (L + 1) + f_i ]

(b_j) 看做横坐标,(f_j + b_j + 2 b_j (L + 1)) 看做纵坐标,这个式子就是一条经过点 (ig( b_j, f_j + b_j + 2 b_j (L + 1) ig)),斜率为 (2 b_i) 的直线,(-b_i^2 - (L + 1) ^ 2 + 2 b_i (L + 1) + f_i) 就看做截距。

转移最优点.PNG

由于 (b_i) 是确定的,截距中除 (f_i) 以外的所有单项式都是常量,所以 (j) 是使该直线截距最小的点,可以看成一条斜率为 (2 b_i) 的直线从 (-infty) 上移过程中碰到的第一个点。
可以维护一个下凸壳,使直线碰到的第一个点一定在凸壳上。

维护凸壳

凸包维护2.png

上图中,决策点的下凸壳为 ((B,C,E)),但因为加入了点 (G),点 (E) 死了(大雾。
可以得到如下结论:
1、凸壳上边的斜率是从左到右单调递增的,否则会出现上图的情况。
2、设凸壳上的点从左至右依次记为 (1)~(k),显然有:在凸壳中加入一个横坐标大于当前所有点的点 (t),若 (slope (k - 1, t) leq slope (k - 1, k)),则 (k) 不在凸壳上。
依照上述做法可以用单调栈来维护凸壳。

找决策点

对于斜率为 (k) 的直线,第一个碰到的点为凸壳上第一条斜率小于 (k) 的线段右端点,若没有则为最左边的点。
例题中,由于 (2 b_i) 的单调性,可以直接用单调队列把不符合上述条件的点扔掉。

一般情况

例题是斜率优化的入门题,当做横坐标和斜率的变量都有单调性,所以很好处理,在更加朴素的情况可以大致分为以下两种:
1、横坐标单调,斜率不单调:不用单调队列,在单调栈上二分即可,或者也可以打平衡树;
2、两者都不单调:我也没写过,平衡树维护应该问题不大。

一些例题

NOI2019d1t1 回家路线

(Sol)

(f_i) 表示乘坐第 (i) 班列车到达站点的最小代价。
暴力转移是显然的。
可以把 第(i) 班列车看作两个事件:
1、出发时刻:从决策点集中选点决策;
2、到达时刻:把第 (i) 个点加入所到达站点的决策点集。
(2m) 个时间按时间排序,就可以用单调队列维护。
(p.s.):由于时间不超过 (1000),直接暴力在凸包上选点可过 (考场亲测)
时间复杂度由于排序可以用 (vector)、链表等时间是线性的 (O(m + T)),其中 (T) 为最大时刻。

(Source)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
int in() {
    int x = 0; char c = getchar(); bool f = 0;
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }

const int N = 1e5 + 5;

struct node {
    int x, t;
} a[N << 2];

std::vector<int> q[N], b[2][1001];
int fro[N];

int n, m, A, B, C;
long long f[N << 1];

inline bool chk1(int j, int k, int M) {
    return f[j - m] - f[k - m] + A * (a[j].t * a[j].t - a[k].t * a[k].t) > M * (a[j].t - a[k].t);
}

inline bool chk2(int j, int k, int i) {
    return (f[j - m] - f[k - m] + A * (a[j].t * a[j].t - a[k].t * a[k].t)) * (a[k].t - a[i].t) >
           (f[k - m] - f[i - m] + A * (a[k].t * a[k].t - a[i].t * a[i].t)) * (a[j].t - a[k].t);
}

int main() {
    //freopen("in", "r", stdin);
    n = in(), m = in(), A = in(), B = in(), C = in();
    for (int i = 1, w, x, y, z; i <= m; ++i) {
        a[i] = (node){in(), in()};
        a[i + m] = (node){in(), in()};
        std::swap(a[i].t, a[i + m].x);
        b[0][a[i].t].push_back(i), b[1][a[i + m].t].push_back(i + m);
    }
    memset(f, -1, sizeof(f));
    for (int tim = 1, i; tim <= 1000; ++tim) {
        for (unsigned p = 0; p < b[0][tim].size(); ++p) {
            i = b[0][tim][p];
            if (a[i].x == 1)
                f[i] = A * a[i].t * a[i].t + B * a[i].t + C;
            while ((int)q[a[i].x].size() > fro[a[i].x] + 1) {
                int j = q[a[i].x][fro[a[i].x]], k = q[a[i].x][fro[a[i].x] + 1];
                if (a[i].t >= a[k].t && chk1(j, k, 2 * A * a[i].t + B))
                    ++fro[a[i].x];
                else
                    break;
            }
            if ((int)q[a[i].x].size() > fro[a[i].x]) {
                int t = q[a[i].x][fro[a[i].x]], tmp = a[i].t - a[t].t;
                if (!~f[i])
                    f[i] = f[t - m] + A * tmp * tmp + B * tmp + C;
                else
                    chk_min(f[i], f[t - m] + A * tmp * tmp + B * tmp + C);
            }

        }
        for (unsigned p = 0; p < b[1][tim].size(); ++p) {
            i = b[1][tim][p];
            if (~f[i - m]) {
                while ((int)q[a[i].x].size() > fro[a[i].x] + 1) {
                    int j = q[a[i].x][(int)q[a[i].x].size() - 2], k = q[a[i].x][(int)q[a[i].x].size() - 1];
                    if (chk2(j, k, i))
                        q[a[i].x].pop_back();
                    else
                        break;
                }
                q[a[i].x].push_back(i);
            }
        }
    }
    long long res = -1;
    for (int i = 1; i <= m; ++i)
        if (a[i + m].x == n && ~f[i])
            if (!~res)
                res = f[i] + a[i + m].t;
            else
                chk_min(res, f[i] + a[i + m].t);
    printf("%lld
", res);
    return 0;
}

updating……

原文地址:https://www.cnblogs.com/15owzLy1-yiylcy/p/11336510.html