写在前面
斯,斜率优化有一段时间没碰了,上次还是在脱产的时候,刚才推了一边式子,发现没有写过博客,于是补一篇qwq
注:例题是cyh教我的一道题,也是我写斜优的第一道题。
正文
我们现在来看一个问题
-
给出n个数,要求按顺序全部取出,每次取出一段所花费的费用为取出一段数的和的平方加m,问最小费用是多少
-
0≤n≤500000,0≤M≤1000
首先,我们不难想到有如下的状态转移方程
dpi=dpj+(sumi−sumj)2+m
sum数组表示前缀和。
然后我们考虑转移的复杂度是O(n2)的,显然通过不了此题,于是我们考虑进行一下优化。
先把式子进行化展开,得
dpi=dpj+sumi2+sumj2−2sumisumj+m
经整理,得
dpj+sumj2=2sumisumj+(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;
}