• 斜率优化初步


    use:https://imgse.com/

    引言(瞎扯),听_rqy大佬说斜率优化更应该叫做截距优化
    rqy大佬说的好像很有道理qwq。


    斜率优化,概括的来讲就是优化形如(f_i=a_i+Max^{i-1}_{j=0}(y_j-k_ix_j))其中(k_i)是递减的(或(Max)换成(Min)(k_i)是递增的)的一种方法

    其中(a_i)是一个和(j)无关的常数,然后我们看(Max)中,就只有(k_i)是与(j)无关的常数

    如果暴力(dp)的话,时间复杂度就是(O(N^2)),我们还需考虑优化

    状态是(O(N))的,已经够优了。考虑优化转移到(O(1))

    我们将方程中的(y_j,x_j)拿出来,看做点((x_j,y_j)),然后放到笛卡尔坐标系中(嘿,真洋呼

    像这样

    然后将直线(y=kx+b)带入

    然后我们选取一点,算出将直线(y=kx+b)平移至改点,然后算出平移后直线的截距
    (y-y_i=k(x-x_i))
    (y=kx+y_i-kx_i)截距就是(y_i-kx_i)。发现就是所需的数值

    然后我们揭下来就是利用其加速决策.
    假设我们在进行(k_i)时的决策,我们,我们可以找到一条(y=kx)的直线,然后在我们1~i点组成的图形中找到一个截距最大的点,就可以进行转移了。很巧的是,这条符合条件的直线只经过这个点,不经过其他点之间之间的连线(或只有多个点)

    such as

    然后我们还有个性质,(k_i)是递减的,然后我们在进行决策时候,就可以让上图中红线的绕这个图形进行旋转,随着(i)的增加,逐渐转到绿线所到的位置。

    就像这样(ps:作图时没考虑好qwq

    然后我们就会发现,有些点是我们考虑都不会考虑的,如下图所示

    这一部分我们就丢掉不管了,反正他不是最优的。

    然后我们发现我们进行完这一步,就好像凸出来一块。然后我们按照如此规则,修整一下整个图形

    这个东西叫做凸壳

    到此,我们先回忆一下。如何利用凸壳进行决策

    然后对于一个(i),我们先取得(k_i),然后再从凸壳(1-i)中找到一条直线(y=kx+b),使得其截距最大。

    而所找到的直线不会穿过凸壳内部。

    凸壳是怎么来的呢?在(k)递减的情况下,绕所有点所围成的。(就像墙上一堆钉子,拿一根小木棍绕着转,决定小木棍的方向的是最外边的两个点,而不是其内部的点,而最外面的点相邻的,对小木棍方向有决定的点连线,形成的图形就是凸壳(马马虎虎qwq))


    好,然后我们再来考虑维护凸壳,以及如何具体使用凸壳

    如何维护插入一个点,使用单调队列,在我们改变直线斜率时,也就是旋转直线时,如果是顺时针旋转,则不用干其他的操作。如果是逆时针旋转,则需要进行一些维护操作

    如下图

    需要我们介入,将(C)点删除,那如何程序实现呢?

    先不急,我们再来看一个更极端的栗子。

    我们如何操作呢?

    贪心的想,我们只比较(E,F)两个点,然后我们分别算一下新增点到(E,F)两条直线的斜率,若发现(F)的斜率比(E)的直线的斜率大,那我们就删除(F)(E)点何时删除呢?

    在我们比较(D,E)是删除,所以我们就使用单调队列,里面是斜率单减的。

    void push(int i)//将第i个点加入
    {
        while(tail-head>=1)//队列中至少要留两个元素,另外,因为我比较毒瘤,所以我的队首和队尾都是实指
        {
            int a=queue[tail],b=queue[tail-1];//去出后两个元素
            if((y[i]-y[a])*(x[i]-x[b])>(x[i]-x[a])*(y[i]-y[b]))//比较,听_rqy说是叉积
            //好高级的样子,其实就是两个截距式比较(我也不知道叫什么,必修二一点也没学,其实读者将左右对调对调,成为分数的形式,就能看出来为什么了。写成乘法快,还可以避免爆精)
                tail--;
            else	break;
        }
        queue[++tail]=i;
    }
    

    然后就是取队首

    暴力的想,每次按遍历所有点,取最大值

    然后发现,竟然是单峰呀。然后我们再来改变斜率(递减),发现峰值所在的位置竟然递增

    好好好,单调队列qwq。

    其实这里你可以想象,一条直线就真的在凸壳上旋转,然后就会有个支点,使得这条直线绕他旋转,这个点就是当时直线截距最大的点。然后总是要离开这个支点的,然后到了下一个支点,下一个支点就是在另一个斜率时的峰顶qwq

    void pop(int i)
    {
        while(tail-head>=1)//需要保存两个点
        {
            int a=queue[head],b=queue[head+1];//取出前两个值
            if(y[a]-k[i]*x[a]<y[b]-k[i]*x[b])//保持单调性,排除掉不是最优解
                head++;
            else	break;
        }
        return ;
    }
    

    end.

  • 相关阅读:
    125-PHP类__set()魔术方法
    124-PHP类析构函数
    123-PHP类构造函数
    122-PHP类成员函数(三)
    121-PHP类成员函数(二)
    120-PHP调用成员方法并将不同类的对象做为参数
    119-PHP调用private成员的方法
    118-PHP调用带参数的成员方法
    117-PHP在外部无法调用private类成员函数
    HDU-2045 不容易系列之(3)—— LELE的RPG难题 找规律&递推
  • 原文地址:https://www.cnblogs.com/Lance1ot/p/9418936.html
Copyright © 2020-2023  润新知