斜率优化讲解

斜率优化讲解

<p align="right"> ——by ysy </p>php

1、简单的复习

       我在这里给出一个式子,$f[i]=max(g[i]+calc(j))$,这是绝大部分dp式子的最基本的模型,每一道题可能只是将$max$改成$min$,或者是将calc中的东西更改一下,你们思考一下是否是这样的。c++

        若是当calc之中的每一项都只含有$i$或者是$j$,而且这两个字母没有相乘的状况咱们就能够用单调队列,这个不难理解,举个例子,像下面的这个式子:$f[i]=max { f[j]+a∗num[j] }$,就能够用单调队列维护,由于整个式子之中只有关于$i$的单独项和关于$j$的单独项。函数

        可是像这样的式子就不能够了:$f[i]=max { f[j]+(sum[i]+sum[j])^2 }$,由于这个式子展开后就会出现关于$i$的式子乘上关于$j$的式子。像这样的式子就是斜率优化的适用范围。优化

2、斜率优化

        像斜率优化这样知识点须要一道例题来进行讲解。下面咱们来看一道经典的例题spa

1.列方程

        咱们先想这道题的dp式子,先无论时间复杂度的问题。code

        这个式子应该很好想:$f[i]=min { f[j]+( \sum_{k=j+1}^{i} lenth[k] +i−j−l)^{2} }$,咱们来分析一下时间复杂度:$O(n^3)$。blog

        想一下优化,咱们是否是能够将求和部分写成前缀和的形式?将$\sum$的部分化成$sum[i]$。这样咱们就能够将式子转化成$f[i]=min { f[j]+( sum[i] -sum[j] +i−j−l)^{2} }$,这样的话时间复杂度就下降成为$O(n^2)$。时间是更低了,可是仍是过不了啊,这是咱们就要等价地变换式子,使其成为y=kx+b的形式,这个形式就是斜率优化的核心。队列

2.转化式子

        $f[i]=min { f[j]+( sum[i] -sum[j] +i−j−l)^{2} } \downarrow$get

        $f[i]= f[j] + [ ( sum[i] + i ) - ( sum[j] + j ) - l ]^2 \downarrow$io

        令$s[i]=sum[i]+i \downarrow$

        $f[i]=f[j]+( s[i] -s[j] - l)^2$

        $f[i] = f[j] + s[i]^2 + ( s[j] + l ) ^2 - 2\times s[i] \times ( s[j] + l) \downarrow$

        $f[j] + s[i]^2 + ( s[j] + l )^2 = 2 \times s[i] \times ( s[j] +l ) + f[i]$

3.分析式子

        $f[j] + s[i]^2 + ( s[j] + l )^2 = 2 \times s[i] \times ( s[j] +l ) + f[i]$

        观察上面的式子,咱们发现这个式子十分像一种函数,y=kx+b,可能你们会有疑问,这个式子和直线的表达是有什么形似之处呢?

        咱们将$f[j] + s[i]^2 + (s[j] + l)^2$这个部分看作一个总体记为$y$,这个部分能够当作一个总体的条件是:这个总体中的全部部分都是已求出的,而且当知道$i$和$j$以后能够$O(1)$求出。显然这个总体知足。同理咱们将$2 \times s[i]$和$(s[i] + l)$这两个部分也分别看作总体,并分别记为$k$、$x$。这样式子就化为$y=kx+f[i]$。

        下一步,咱们创建以个平面直角坐标系,这个平面直角坐标系中的每个点的坐标$(x,y) ​$都对应的是上面式子中的$x ​$和$y ​$,这样咱们就可以将每个与$i ​$有关的东西处理完事以后标到平面直角坐标系之中。每个点的坐标表成$( s[i],f[i] + (s[i] + l)^2 ) ​$,可能有人会问为何纵坐标没有了$s[i]^2 ​$,而且横坐标没有了$l ​$,这个问题下面会解答,请稍做等待。

        若是咱们想用$j$来转移$i$的话,就要让斜率为$2 \times s[i]$的直线过点$(s[j],f[j] + (s[j] + l)^2)$,而且此时直线的截距就是新的$f[i]$,由于$f[i]$为这条直线的$b$。再看下面的图解,咱们将求过的点都标到平面直角坐标系中,咱们能够发现,咱们想过的这个点必定在咱们维护的大圆包上,像点2这样的点就不能被用来更新,由于过点3所得截距,必定比过点2所得截距小,那么咱们能发现当点3求出以后,只要比较一下,点2和点3造成的直线的斜率和点1和点2造成的直线的斜率,若是二、3造成的比一、2造成的要小,那么3号点必定比2号点更优。咱们再看,假设下图之中已经维护好1到5的全部点,那么就会出现这样的大圆包。咱们用求出6的点的直线去和这些点相交,咱们发现只有点4在当前直线上时能使截距最小(画一画图就能发现是过点4时,直线的截距最小),根据是由点4转移,咱们能够发现,当两个点一、3的斜率小于$2 \times s[i]$的时候,横坐标小的点必定不能用来转移,同理斜率大于$2 \times s[i]$的两个点,横坐标大的也不可以用来转移,这个性质是否是很好?

        根据上面咱们发现的式子,咱们能够维护一个相似于单调队列的队列来维护咱们的大圆包。可是这个大圆包具体怎么维护呢?咱们先看如何求斜率。若是给你直线上的两个点,我想你们必定会求斜率。就是两点的纵坐标相减的差除上两点的横坐标相减的差。这里也就解释了,为何上文中的纵坐标没有了$s[i]^2$,由于两式相减时$s[i]$是相同的,从而$s[i]^2$也就是相同的,因此相减时就将其减掉了,所以$s[i]^2$不用出如今纵坐标之中。同理在相减时咱们的横坐标也不须要$l$。

double re_x(int i){return s[i];}
double re_y(int i){return f[i]+(s[i]+l)*(s[i]+l);}
double re_k(int i,int j){return (re_y(j)-re_y(i))/(re_x(j)-re_x(i));}

        会求斜率了,咱们再来看怎么维护大圆包,咱们发现当队列中最后一个的点和队列中倒数第二个点的产生斜率大于最后一个点和新产生的点产生的斜率,那么结尾就要弹出队列,这个用一个$whlie$循环就可以解决,最后再将新产生的点放在结尾。这个实现十分像单调队列的实现。

int main()
{
    while(head<tail&&re_k(q[tail],i)<re_k(q[tail],q[tail-1])) tail--;
    q[++tail]=i;
}

        咱们再看,怎么知足第二个性质,让更新变成$O(1)$的?咱们发现当队列中第一个点和第二个点产生的斜率若是小于当前的直线,那么第二个点更新必定比第一个点更新更优,咱们就要进行队首弹出。这个过程也十分像单调队列的维护。最后直接用队首进行更新。

int main()
{
    while(head<tail&&re_k(q[head],q[head+1])<2*s[i]) head++;
    f[i]=f[q[head]]+(s[i]-s[q[head]]-l-1)*(s[i]-s[q[head]]-l-1);
}

        这样咱们就解决了维护的问题,最后就是将这些组装在一块儿,造成下方的代码。

#include <stdio.h>
#define N 50001
int n,l,head,tail;
long long f[N],s[N],q[N];
double re_x(int i){return s[i];}
double re_y(int i){return f[i]+(s[i]+l)*(s[i]+l);}
double re_k(int i,int j){return (re_y(j)-re_y(i))/(re_x(j)-re_x(i));}
int main()
{
    scanf("%d%d",&n,&l);
    for(int i=1;i<=n;i++)
		scanf("%lld",&s[i]),s[i]+=s[i-1];
    for(int i=1;i<=n;i++) s[i]+=i;
    q[tail]=0;
    for(int i=1;i<=n;i++)
    {
		while(head<tail&&re_k(q[head],q[head+1])<2*s[i]) head++;
		f[i]=f[q[head]]+(s[i]-s[q[head]]-l-1)*(s[i]-s[q[head]]-l-1);
		while(head<tail&&re_k(q[tail],i)<re_k(q[tail],q[tail-1])) tail--;
		q[++tail]=i;
    }
    printf("%lld\n",f[n]);
}
4.分析上方代码的适用范围

​        上方的代码是有必定的适用范围的,你们想一下,为何咱们敢弹出队首与队尾?

        咱们再来看一下题目,这个题目显然知足一个特色,就是因为咱们将$s[i]$定义为前缀和,因此他必定是单调递增的,而且咱们的点的横坐标也是$s[i]$也知足单调递增。这两个性质十分好。咱们把队首的元素弹出的条件是斜率小于$2 \times s[i]$,由于$s[i]$知足单调递增,因此弹出时小于,那之后就必定一直小于下去,因此弹出就弹出了。咱们再看,由于咱们的橫坐标知足单调递增,因此每一次插入点都会在最后,所以结尾弹出也是正确的。

​        可是若是斜率没有单调性呢?咱们就不能将队首弹出,这样咱们就不能在$O(1)$的时间内求出新的元素,咱们能够在大圆包上进行二分。咱们看下面的大圆包,会发现只有当前点和上一个点的斜率小于直线斜率,而且和下一个点的斜率大于直线的斜率时,这个点才是最优的。因此咱们能够进行二分查找。

​        若是咱们的横坐标没有单调性呢?咱们就不可以将队尾删掉了,咱们应该用平衡树来维护,动态维护大圆包。可是怎么维护呢?咱们能够运用$splay$,具体请听本人口述。

3、练习

1.仓库建设

        $1)$列方程,并转化形式

                $f[i] = min ( f[j] + x[i] \times ( P[i] - P[j]) + g[i] - g[j] + c[i]) \downarrow$

​                $ f[i] = f[j] + x[i] \times P[i] - x[i] \times P[j] +g[i] -g[j] +c[i] \downarrow$

                $f[j] - g[j] + x[i] \times P[i] +g[i] + c[i] = x[i] \times P[j] + f[i]$

        $2) ​$找点

                显然这里的点就是$( f[j] - g[j] ,P[j] )$,斜率就是$x[i]$,截距就是$f[i]$。

        $3)$写吧

2.剩下的习题

        土地购买特别行动队防护准备序列分割小p的牧场征途

相关文章
相关标签/搜索