斜率优化dp小结

Ⅰ. 斜率优化认知

  • 这东西英文名叫$Convex Hull Trick (CHT)$
  • 主要优化形如$f_i = min\{ f_j + cost(j+1, i) \}的这种dp$
  • 由于等号右边的式子并不能$O(1)$计算,所以我们需要一些技巧来优化

Ⅱ. 斜率优化

  • $懒得写引子了,就直接推推推吧$
  • $对于当前状态i,有2个决策j和k,不妨假设k < j < i,如果决策j优于k,则有:$
    $$f_j + cost(j+1, i)<f_k + cost(k+1, i)$$
  • $如果以上这个式子可以化简到:$
    $$ \frac{Y(j)-Y(k)}{X(j)-X(k)}<F(i) $$
  • $记这个式子为slope(k, j)=\frac{Y(j)-Y(k)}{X(j)-X(k)}<F(i),即j优于k$
  • $便可以使用优化,这个式子很斜率,故称这个优化方法为斜率优化$

  • $我们发现以上并没有什么卵用,先说一个结论$
  • $结论:如果slope(k, j)\ge slope(j, i),k < j < i,那么j决策是不优的可以删除$
  • $证明:$
  • $如果slope(j, i) < F(i),那么i优于j,j可以删除$
  • $如果slope(j, i)\ge F(i),虽然j优于i,但是slope(k, j)\ge F(i),k比j优,j还是可以删除 \blacksquare$
  • $然后就可以用这个结论来维护一个单调队列来搞了$

Ⅲ. 题目讲解

HDU 3507 Print Article

分析:
$f_i:=打印前i个字符的最小代价$
$f_i = min\{ f_j + (sum_i - sum_{j})^2 + M \},sum_i=\sum_{i=1}^n c_i$
$假设k<j<i,假设j优于k,可得:$
$$ f_j + (sum_i - sum_{j})^2 + M < f_k + (sum_i - sum_{k})^2 + M $$
$$ f_j +sum_i^2-2\cdot sum_i \cdot sum_j + sum_j^2 < f_k +sum_i^2-2\cdot sum_i \cdot sum_k + sum_k^2 $$
$$ (f_j + sum_j^2)-(f_k+sum_k^2) < 2\cdot sum_i\cdot (sum_j-sum_k) $$
$$ \frac{(f_j + sum_j^2)-(f_k+sum_k^2)}{sum_j-sum_k} < sum_i $$
$即j优于k的条件是:slope(k, j)=\frac{(f_j + sum_j^2)-(f_k+sum_k^2)}{sum_j-sum_k} < sum_i $

  • $维护单调队列,开区间[L, R):$
  • $先删除队头不优的元素,即slope(q[L], q[L+1])\le sum_i$
  • $此时q[L+1]不差于q[L],所以q[L]可以删除$
  • $用最优的j=q[L]更新f_i$
  • $再用f_i去删除队尾的不优元素,即slope(q[R-2], q[R-1])\ge slope(q[R-1], i)$
  • $时间复杂度O(n)$

参考代码:

//
//  Created by TaoSama on 2016-05-10
//  Copyright (c) 2016 TaoSama. All rights reserved.
//
#pragma comment(linker, "/STACK:102400000,102400000")
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>

using namespace std;
#define pr(x) cout << #x << " = " << x << "  "
#define prln(x) cout << #x << " = " << x << endl
const int N = 5e5 + 10, INF = 0x3f3f3f3f, MOD = 1e9 + 7;

int n, m;
int s[N], q[N];
int f[N];

int up(int k, int j) {
    return (f[j] + s[j] * s[j]) - (f[k] + s[k] * s[k]);
}

int dw(int k, int j) {
    return 2 * (s[j] - s[k]);
}

bool check(int k, int j, int i) {
    return up(k, j) * dw(j, i) >= up(j, i) * dw(k, j);
}

int main() {
#ifdef LOCAL
    freopen("C:\\Users\\TaoSama\\Desktop\\in.txt", "r", stdin);
//  freopen("C:\\Users\\TaoSama\\Desktop\\out.txt","w",stdout);
#endif
    ios_base::sync_with_stdio(0);
    clock_t _ = clock();

    while(scanf("%d%d", &n, &m) == 2) {
        for(int i = 1; i <= n; ++i) {
            scanf("%d", s + i);
            s[i] += s[i - 1];
        }

        int L = 0, R = 0;
        q[R++] = f[0] = 0;
        for(int i = 1; i <= n; ++i) {
            while(L + 1 < R && up(q[L], q[L + 1]) <= s[i] * dw(q[L], q[L + 1])) ++L;
            int j = q[L];
            f[i] = f[j] + m + (s[i] - s[j]) * (s[i] - s[j]);
            while(L + 1 < R && check(q[R - 2], q[R - 1], i)) --R;
            q[R++] = i;
        }

        printf("%d\n", f[n]);
    }

#ifdef LOCAL
    printf("\nTime cost: %.2fs\n", 1.0 * (clock() - _) / CLOCKS_PER_SEC);
#endif
    return 0;
}