• 斜率优化·学习笔记


    ({huge{Step\,1\,一点小转化}})
    事实上斜率优化是专门用来处理这样一类(dp)式子的

    [dpi=Ai+maxj=1i−1(Bj−Cj×basei) ]

    窝萌尝试把上式中的(Bj)(Cj)(basei)等价成(xj)(yj)(ki),并把它们丢到一个平面上,然后它萌就会变成一堆点((xj,yj)),画一条过他们的直线,类似于

    [y−yj=ki(x−xj) ]

    变换一下

    [y=kix+(yj+xj) ]

    窝萌会发现,现在窝萌所求的不过是一条截距最大的直线而已。那么其实就是相当于求一个给定(k)意义下最靠上的点。

    (qwq)那么窝萌不妨先减弱问题的不可做性,使其单调——令(x)单调增、(ki)单调减。

    那当窝萌在朴素(Dp)遍历所有的(ki)时,所得到的直线的轨迹应该是这样的:

    (上图是个(GIF)……不动的话就拖出来看吧)轨迹正好是一个凸壳,并且你会发现它的轨迹正好是再绕着每个斜率下最优的点旋转。

    有个神犇对此是这么解释的:

    可以发现好多点是没有机会作为最优的点的。

    形象⼀点的说,如果⼀个点的左边和右边某两个点在它上⽅“搭起”了⼀条线段,那么它就永远不会被选到。

    而因为我们保证了(x)(k)的单调性,所以就我们可以比较方便的考虑“如何选取当前最优点”这个问题。我们考虑遇到一个新的点,是否把他加入最优解集合里面,其实质就是维护一个上凸壳。那么已知两个点(A)(B),现在遇到了新加入的点(C),此时有两种情况:

    1、(BC)的斜率大于(AB)的斜率
    这时我们需要抛弃(B),直接连接(AC)

    2、(BC)的斜率小于(AB)的斜率
    通过这种方式我们就可以完成对整个凸壳的维护。而在判定时也很简单,用叉积来判断即可,即有(A)(B)(C)三个点分别是((xA,yA),(xB,yB),(xC,yC))满足(xA≤xB≤xC),那么如果

    [(xC−xA)(yB−yA)−(xB−xA)(yC−yA)<0 ]

    则证明是第一种情况,否则是第二种。

    (emmmm)直接求斜率当然也是可以的吧,不过会不会很慢很麻烦啊(qwq)

    ({huge{Step\,2\,代码实现以及拓展}})
    (emmm)我们考虑用一个逻辑上单调的队列来实现去掉不优的状态 +加入新的状态。回归上题,我们所求的是(max),所以我们需要去掉那些斜率小的状态;同时需要加入斜率大的状态。由于整个过程牵扯到前后删点,所以用双端队列来维护。本蒟蒻的(伪)代码如下:

    int queue[MAXN] ;
    int head = 0, tail = 0 ;
    for(i = 1; i <= N; i ++){
        while (1){
            A = queue[head], B = queue[head + 1] ;
            if (y[A] - k[i] * x[A] < y[B] - k[i] * x[B]) head ++ ;
            else break ;
        }
        dp[i] = base[i] + y[A] - k[i] * x[A] ;
        Maybe y[i] needs calcing ?
        Maybe x[i] also needs calcint ?
        So, Calc_it() ;
        while (tail - head >= 2){
            A = queue[tail - 1], B = queue[tail] ;
            if ((y[i] - y[A]) * (x[B] - x[A])
              - (x[i] - x[A]) * (y[B] - y[A]) > 0) 
                tail-- ;
            else break ;
        }
        queue[++ tail] = i ;
    }
    

    对了……我在啃这个地方时出了个(bug)……那是因为我一直以为(y)(x)从本质上来讲有(n^2)个……(Orz)
    那么对于拓展而言,我们以上做的一切都是基于“(x)(k)单调”这一前提的,那么还会有以下两种情况:

    1、(x)不单调

    那么实质上就是我们需要动态插入删除、从内部删除,那么就需要用平衡树来维护一个凸包了 。

    2、(k)不单调

    那么实质上就是我们原来比较方便的第一个(while)——从左往右直接删是不行的了,因为现在不优不代表之后不优,所以此时我们需要的就是三分一个位置而不是从前向后扫、

    哦,还有,对于(dp)式子而言,如果它长这个样子:

    [dpi=Ai+minj=1i−1(Bj−Cj×basei) ]

    那么其实我们维护的就是一个下凸壳,所以此时只需要把所有的大于号改成小于号即可。

  • 相关阅读:
    基本二叉搜索树的第K小元素
    sklearn常见分类器(二分类模板)
    python图论包networks(最短路,最小生成树带包)
    PAT 甲级 1030 Travel Plan (30 分)(dijstra,较简单,但要注意是从0到n-1)
    PAT 甲级 1029 Median (25 分)(思维题,找两个队列的中位数,没想到)*
    Oracle 10g ORA-12154: TNS: could not resolve the connect identifier specified 问题解决! 我同事遇到的问题。 username/
    JavaScritpt的DOM初探之Node(一)
    怎样实现动态加入布局文件(避免 The specified child already has a parent的问题)
    Ubuntu 14.04下单节点Ceph安装(by quqi99)
    卡片游戏
  • 原文地址:https://www.cnblogs.com/sqrthyy/p/9864855.html
Copyright © 2020-2023  润新知