【算法笔记】斜率优化

写在前面

斯,斜率优化有一段时间没碰了,上次还是在脱产的时候,刚才推了一边式子,发现没有写过博客,于是补一篇qwq

注:例题是cyh教我的一道题,也是我写斜优的第一道题。

正文

我们现在来看一个问题

  • 给出n个数,要求按顺序全部取出,每次取出一段所花费的费用为取出一段数的和的平方加m,问最小费用是多少

  • 0≤n≤500000,0≤M≤1000

首先,我们不难想到有如下的状态转移方程

dpi​=dpj​+(sumi​−sumj​)2+m

sum数组表示前缀和。

然后我们考虑转移的复杂度是O(n2)的,显然通过不了此题,于是我们考虑进行一下优化。

先把式子进行化展开,得

dpi​=dpj​+sumi2​+sumj2​−2sumi​sumj​+m

经整理,得

dpj​+sumj2​=2sumi​sumj​+(dpi​−sumi2​+m)

如果我们把带i的项看做常数,那么这个式子就被化为了形如y=kx+b的式子

也就是一次函数。

我们发现,如果把每一对(x,y)放到二维坐标系内,那么则一定有一些点不会是最优决策。

这里我也说不清楚,去看oi−wiki吧

然后我们把那些有可能作为最优决策的点连接起来,我们会发现它构成了一个凸包的下边界(当然有的题是上边界)。

而且所构成的斜率是单调递增的

那么我们可以用一个单调的队列来维护凸包就可以了

注意,这里不是单调队列这个数据结构。

还要说一下斜率的计算公式是:(xi−xj)/(yi−yj)​

贴一下代码捏

#include <bits/stdc++.h>
using namespace std;
const int N=500010;
int n,m;
int a[N];
int sum[N];
int q[N];
int f[N];
int X(int x)
{
    return sum[x];
}
int Y(int x)
{
    return f[x]+sum[x]*sum[x];
}
int main()
{
    while(cin>>n>>m)
    {
        memset(f,0,sizeof f);
        memset(q,0,sizeof q);
        memset(sum,0,sizeof sum);
        for(int i=1;i<=n;i++)
            cin>>a[i],sum[i]=sum[i-1]+a[i];
        int hh=0,tt=-1;
        q[++tt]=0;
        for(int i=1;i<=n;i++)
        {
            while(hh<tt&&Y(q[hh+1])-Y(q[hh])<=(2*sum[i])*(X(q[hh+1])-X(q[hh])))hh++;
            int j=q[hh];
            f[i]=f[j]+(sum[i]-sum[j])*(sum[i]-sum[j])+m;
            while(hh<tt&&(Y(q[tt])-Y(q[tt-1]))*(X(i)-X(q[tt]))>=(X(q[tt])-X(q[tt-1]))*(Y(i)-Y(q[tt])))tt--;
            q[++tt]=i;
        }
        cout<<f[n]<<endl;
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值