• 斜率优化学习笔记


    例题 : HNOI玩具装箱

    朴素的DP

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

    [dp(i)= displaystyle min_{1leq 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
    ", 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
    ", 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)的决策点
  • 相关阅读:
    制造者为什么重要
    归因理论
    初创业谨记有三法宝:顶梁柱、现金牛、北极星
    华特迪士尼语录
    说好一个创业故事的5个步骤
    接口
    抽象类_模板方法设计模式
    抽象类与抽象方法
    非static和static初始化块
    单例设计模式
  • 原文地址:https://www.cnblogs.com/Eroad/p/9373263.html
Copyright © 2020-2023  润新知