BZOJ1911 [Apio2010]特别行动队 斜率优化
Description
给定一个序列与一个二次函数f(x),将序列连续分段使得所以区间和的二次函数和最大。求最大值。
题解
我们可以很容易想出状态转移方程,s表示前缀和。
f[i]=max(f[i],f[j]−F(s[i]−s[j]));
可是这样的复杂度有 O(n2)
铁定超时,所以得想办法优化。
因为我们可以看出F(s[i]-s[j])是由i与j决定的而不是单单由i觉定,所以不可以用简单的单调栈优化,但是可以用斜率优化。
我们可以将方程变形
f[i]=f[j]+a(s[i]−s[j])2+b(s[i]−s[j])+c
即
f[i]=f[j]+as[i]2−2as[i]s[j]+as[j]2+bs[i]−bs[j]+c
假设 k<j<i ,求出f[i]时,决策j比决策k优,我们有
f[j]+as[i]2−2as[i]s[j]+as[j]2+bs[i]−bs[j]+c>
f[k]+as[i]2−2as[i]s[k]+as[k]2+bs[i]−bs[k]+c
看着不不好看,我们整理得,将s[i]移到右边,
f[j]−f[k]+[a(s[j]−s[k])−b](s[j]−s[k])s[j]−s[k]>2as[i]
啊吼?
于是不等式就变成左边一个与i,k,相关的分式,也就是斜率,右边是只与i有关的式子。而且惊奇的发现右边是随着i的增大而增大。
为什么可以依靠斜率单调性来进行决策呢?我们记g[i,j]为i,j的决策
同样是
k<j<i
,如果g[i,j]>g[j,k],
假设 g[i,j]>g[j,k]>2as[i],就代表j比k优,i比j优。
假设 2as[i]>g[i,j]>g[j,k],就代表j比i优,k比j优。
无论哪种情况j都不是最优决策,所以j这个决策没必要记录,也就是所有决策中不能存在一个g[i,j]>g[j,k]的情况。
去掉g[i,j]>g[j,k],我们就可以发现决策斜率构成的图像是一个斜率单调递减的队列。
维护好这个队列,对于i的决策就是 第一个满足
g[q[h+1],q[h]]<=2as[i]
的q[h](队头)。因为2as[i]是单调递增,所以可以将排除掉的队头直接出队。
而求出一个f[i]时也要入队,但入队时要维护单调性。
就·这·样,我们可以用斜率优化来进行决策,而且不用二分找,决策复杂度O(1),维护单调队列一共O(n)。
#include <stdio.h>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MAXN 1000000+10
#define LL long long
using namespace std;
LL a,b,c,x[MAXN],f[MAXN],sum[MAXN];
int n,q[MAXN],h,t=1;
LL F(LL x) {return x*x*a+b*x+c;}
LL up(int i,int j) {return f[i]-f[j]+((a*(sum[i]+sum[j]))-b)*(sum[i]-sum[j]);}
LL down(int i,int j) {return sum[i]-sum[j];}
int main()
{
scanf("%d%lld%lld%lld",&n,&a,&b,&c);
for(int i=1;i<=n;i++) scanf("%lld",&x[i]),sum[i]+=sum[i-1]+x[i];
f[0]=0;q[1]=0;h=1;t=2;
for(int i=1;i<=n;i++)
{
while(h<t-1&&up(q[h+1],q[h])>2*a*sum[i]*down(q[h+1],q[h])) h++;
f[i]=f[q[h]]+F(sum[i]-sum[q[h]]);
while(h<t-1&&up(q[t-1],q[t-2])*down(i,q[t-2])<up(i,q[t-2])*down(q[t-1],q[t-2])) t--;
q[t++]=i;
}
printf("%lld\n",f[n]);
return 0;
}