斜率优化学习笔记

例题 : HNOI玩具装箱

朴素的DP

由题意我们可以马上得到DP方程:

\[dp(i)= \displaystyle \min_{1\leq j< i}\{dp(j)+(sum(i)-sum(j)+i-j-1-L)^2\} \\sum(x) 为前缀和 \]

很可惜, 这个解法是\(O(n^2)\)的, 无法AC (但是可以在当年骗很多分?)

接下来的文章中我们会用几个简称

\[f(i) = sum(i)+i \\ C = 1+L \]

第一类斜率优化

优化dp的思路有两种 :

  • 优化状态转移方程
  • 优化决策(排除不可能决策) 如:单调队列优化

我们无法再进行第一种优化了(方程是\(1D/1D\)的), 所以只能进行第二种优化

考虑i的两个决策点j,k, 其中 j < k
如果k优于j, 那么

\[dp(k) + (f_i-f_k-c)^2 < dp(k)+(f_i-f_j-c)^2\\ 左边整理得 dp_k+ (f_i^2-2f_i(f_k+c)+(f_k+c)^2)\ \ , 右边同理 \\令Y_k = dp_k +(f_k+c)^2, X_k = f_k+c\\ 再次整理得 \frac {Y_k-Y_j} {X_k-X_j} < 2f_i \]

这不就是斜率的代数式吗?

注意, 在此处用到了一个性质: 对于 k>j , X(k) - X(j) > 0

也就是DP(k) > DP(j)

这是第一类斜率优化的必要条件, 该题中可通过 打表 得知.

记住, 我们现在得到的结果是:

\(j<k\)时, 若满足$ \frac {Y_k-Y_j} {X_k-X_j} \leq 2f_i$ , 则 决策k优于决策j, 否则决策j 优于决策 k.

之后的文章里, "决策k优于决策j" 简写为 k B j.

通过这个结果, 我们可以得知: 可选决策的集合斜率单调递增, 形成一个下凸壳.

为什么? 下面是证明:

\(设有3个决策x, y, z, 其中x<y<z\)
$如果 hyp(x, y) > hyp(y, z) , 那么y不可能是最优决策点. $

分两种情况讨论:

1: hyp(x, y) > 2*f(i)

该情况不满足上述不等式, 得到 x B y. 排除 y.

2: hyp(x,y) < 2*f(i)

该情况满足上述不等式, 得到 y B x.

但是由于 \(hyp(x, y) > hyp(y, z)\), 所以推出\(hyp(y, z) < 2*f(i)\), 得到 z B y

综上, y必定会被排除.

我们知道了可选决策集合, 那么怎么快速得知最优决策呢?

思考凸壳的性质.

可以得知在凸壳上必定有一点 y 它的前驱是x, 后继是z, 其中\(hyp(x,y) < 2*f_i < hyp(y,z)\)

那么可以得知**y B x, y B z **

二分斜率 \(2f_i\)即可. (请读者自己思考全体斜率大于/小于\(2f_i\)的情况)

如何维护最优决策呢?

直接按照graham扫描法用单调栈\(O(n)\)维护凸壳即可.

复杂度\(O(n\ logn)\)

代码呼之欲出.

code

#include<bits/stdc++.h>
using namespace std;

inline int read(){int ans = 0, f = 1; char c = getchar();while(c < '0' || c > '9') f = (c == '-') ? -1 : f, c = getchar();while('0' <= c && c <= '9') ans = ans*10 + c - '0', c = getchar();return ans;}

#define ld long double 
#define int long long
const int maxn = 5e4+5;
int dp[maxn], sum[maxn];
int n, L;

inline int v1(int p){
    return sum[p] + p;
}

inline int sqr(int x){return x*x;}

//hypotenuse
int v2;
inline ld hyp(int a, int b){
	if(v1(a) == v1(b)) return -1e9; 
    return (ld)(dp[a] + sqr(v1(a)+v2) -dp[b] - sqr(v1(b)+v2)) / (v1(a) - v1(b));
}

int q[maxn*2], l, r;
void solve(){
    l = 0, r = 1; q[++l] = 0; 
    v2 = 1+L;
    for(int i = 1; i <= n; i++) {
        //凸壳斜率单调递增
        while(l < r && hyp(q[l], q[l+1]) < 2*v1(i) )
            l++;
        //这里我没有用二分, 因为斜率2*f(i)也是单调递增的.
        //将单调栈改为维护单调队列就可以得到答案.
        int opt = q[l];//optimal point
        dp[i] = dp[opt] + sqr(v1(i) - v1(opt) - v2);
		//printf("%lld\n", dp[i]);

        while(l < r && hyp(q[r-1], q[r]) > hyp(q[r], i)) r--;
        q[++r] = i;
    }
}

signed main(){
    n = read(), L = read();    
    for(int i = 1; i <= n; i++){
        int tmp = read();
        sum[i] = sum[i-1] + tmp;
    }

    solve();
    printf("%lld\n", dp[n]);
    return 0;
}

第二类斜率优化

题目: NOI2007 货币兑换

该题斜率方程不满足性质 : 对于 k>j , X(k) - X(j) > 0

看的出来这是一个二维偏序模型, 可用CDQ分治维护凸壳.

蒟蒻写挂了, 求神犇指教.

注意事项(Q/A)

Q: 什么时候可以用斜率优化?

A: 1D/1D方程, 决策单调不等式左边像个斜率, 右边 hyp 只与 \(i\) 有关

"像个斜率"意味着不等式左边不一定是$\frac {Y_k-Y_j} {X_k-X_j} $, 也可以是 \(\frac {Y_k-Y_j} {X_j-X_k}\)

毕竟在证明的过程中, 我们没有用到任何斜率的性质, 一直是在推不等式 , 对吧?

Q: 怎么推式子?

A: 记住决策不等式前提是: k>j, 意义是:

\(j<k\)时, 若满足$ \frac {Y_k-Y_j} {X_k-X_j} (\leq或\geq ) hyp$ , 则 决策 k 优于决策 j , 否则决策 j 优于决策 k .

Q: 维护上/下凸壳?

A: 上式为大于号时 维护上凸壳, 为小于号时维护下凸壳.

关于打表

DP打表要打 2 个值:

  • dp(i)
  • dp(i)的决策点
posted @ 2018-07-26 17:39  懿路智行  阅读(185)  评论(0编辑  收藏  举报