斜率优化初步

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.

posted @ 2018-08-04 16:11  Lance1ot  阅读(452)  评论(0编辑  收藏  举报