斜率优化总结

首先,推荐一个大佬的博客,讲解非常详细,所以不会斜率优化的请移步这里,本博客主要讲题目分析qwq

https://www.cnblogs.com/yangsongyi/p/9630227.html

主要讲解题目:

1.[HNOI2008玩具装箱]

2.[ZJOI2007仓库建设]

3.[USACO08MAR土地征用Land Acquisition]

4.[APIO2010特别行动队]

下面,开始吧。


 

1.[HNOI2008玩具装箱]

  题目链接:https://www.luogu.org/problemnew/show/P3195

  题目分析:

  /*   如何看出它是个斜率优化的题呢?

  first:我们可以轻松的看出它是个DP

  next:数据范围对于 $O(n^2)$显然不大友好

  then: 怎么办呢?      肯定是得优化的,但怎么优化,用什么优化才是关键。*/

  估计很多读者(呃。。such as本人)想到这里,直接的反应是:(算了,算了,不管了,写个DP式子,拿个暴力分得了) 于是对于此题,经过一番分析后,我们可以发现,有好几种方法列出状态及转移方程。

  比如说$O(n^3)$的区间DP,读者们是可以较轻松列出的,但是这样的话,我们惊讶的发现:这玩意儿没办法优化,(写的极其优美的情况下)才是$O(n^3)$的。   此种——只适合暴力。

  再想想,有没有$O(n^2)$的做法。

  当然是有的,我们可以将状态这样设: $f[i]$ 表示装到第$i$个玩具,最小的花费是多少。   这样我们接下来可以直接枚举上一个打包压缩的位置$j$,用$f[j]$来更新我们的$f[i]$,时间复杂度$O(n^2)$。

  方程是这样的$f[i]=min{(f[j]+(sum[i]-sum[j]+i-j-l)^2}$ 其中$sum$数组维护的是题目中$c$数组的前缀和(为了计算费用方便)

  $!!!$接下来的过程才是重头戏,经过上面dalao博客的学习后,我们知道了斜率优化这个东西,它的本质是要将上面的转移方程转化为 $y=kx+b$的形式,其中$b$就是我们要求的$f[i]$。(方便求)

  于是,开始了漫长的导式子过程:  (部分摘自上面那位的blog......)

  我们可以知道,所有只和$i$有关的项,和常数项可以暂时拿掉(相当于放在$b$旁边),最后再加,因为它是个定值,对答案没有影响,而且在以后的相减过程中,都是可以消去的项(大家不妨可以保留后试一试,看看对答案是否有影响)。

  然后呢,我们令 $s[x]=sum[x]+x$

    上面那个式子就成了这样: $f[i]=f[j]+s[i]^2+(s[j]-l)^2-2*s[i]*(s[j]-l)$

  移项:$f[j]+s[i]^2+(s[j]+l)^2=2*s[i]*(s[j]+l)+f[i]$

  省略式子(简化):$f[j]+(s[j]+l)^2=2*s[i]*(s[j]+l)+f[i]$

  然后这一坨  $f[j]+(s[j]+l)^2$ 就是 $y$ ,$2*s[i]$就是$k$,$f[i]$便是$b$,$s[j]$便是$x$(至于$l$为什么可以拿掉,因为在维护上面的式子时,我们需要做差,这一过程后,我们发现,$l$消失了。。)。

  finally:可以用斜率优化的一贯做法来解题了。 首先要看好,要维护的凸包是向上的,还是向下的。(用人话来说,就是看看这个题是维护最小的花费,还是最大的花费。)然后建个平面直角坐标系,每个点的坐标便是 $(s[j],f[j]+(s[j]+l)^2)$ ,然后。。在此就不赘述了,剩下的上面那篇博客里就都有了,而且也是些套路的问题。

  $!!!$这里有个比较重要的问题,就是我们一定一定要看好斜率$k$的符号,以便处理一些细节问题。

  以下是代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=50001;
int n, l;
long long s[N];
long long f[N];
long long q[N];
double f_x(int x)
{
    return s[x];
}
double f_y(int x)
{
    return f[x]+(s[x]+l)*(s[x]+l);
}
double f_k(int a,int b)
{
    return (f_y(a)-f_y(b))/(f_x(a)-f_x(b));
}
int main()
{
    scanf("%d%d",&n,&l);
    for(int i=1;i<=n;i++)
    {
        long long x;
        scanf("%lld",&x);
        s[i]=s[i-1]+x;
    }
    int head, tail;
    head=tail=0;
    for(int i=1;i<=n;i++)
        s[i]+=i;
    for(int i=1;i<=n;i++)
    {
        while(head<tail&&f_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&&f_k(q[tail],i)<f_k(q[tail],q[tail-1]))
            tail--;
        q[++tail]=i;
    }
    printf("%lld",f[n]);
}

2.[ZJOI2007仓库建设]

 // 以下题均为略讲,如有特殊需要注意的地方,会加重处理。

   题目链接:https://www.luogu.org/problemnew/show/P2120

   呃。。其实我最初分析这道题时,认为和上一道没什么两样。。。(其实真没什么两样),比较裸的概率DP,做起来,代码看起来,都差不多。(未完待续。。。)

posted @ 2018-09-13 21:02  sky20030724  阅读(190)  评论(0编辑  收藏  举报