题意
给出一个常数a,b,c和数列{ xn },将其分成若干段每一段至少有一个数,并且每一段将产生一个贡献为 a∗x2+b∗x+c
找到一种分组方案使贡献和最大
解题报告
前两天写的没来得及写博客现在找不到推式子的草稿纸了…只好再推一遍…
斜率优化
设置状态 fi 表示分到第 i 个数,且以这个数为当前最后一组结尾的最大贡献和
转移时若有
f[j]+a∗(sum[i]−sum[j])2+b∗(sum[i]−sum[j])+c<f[k]+a∗(sum[i]−sum[k])2+b∗(sum[i]−sum[k])+c
整理,得
sum[i]>(f[j]+a∗sum[j]2−b∗sum[j]−(f[k]+a∗sum[k]2−b∗sum[k]))/(2∗a∗(sum[j]−sum[k]))
局势渐渐明朗了…右边可以看做斜率的形式,左边与j,k无关
因为求最大,其实就是维护右边的斜率单调不减,单调队列维护一下就好了
#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 1000005
#define LL long long
#define inf 1e18
using namespace std;
int n,q[N];
LL sum[N],a,b,c,f[N];
inline double calc(int x,int y){return (double)(f[x]-f[y]+a*(sum[x]*sum[x]-sum[y]*sum[y])+b*(sum[y]-sum[x]))/(double)(2*a*(sum[x]-sum[y]));}
int main(){
scanf("%d%lld%lld%lld",&n,&a,&b,&c);
for(int i=1;i<=n;++i) scanf("%lld",&sum[i]),sum[i]+=sum[i-1];
int l=0,r=0;
for(int i=1;i<=n;++i){
while(l<r&&calc(q[l],q[l+1])<sum[i]) ++l;
LL tmp=sum[i]-sum[q[l]];
f[i]=f[q[l]]+a*tmp*tmp+b*tmp+c;
while(l<r&&calc(q[r-1],q[r])>calc(q[r],i)) --r;
q[++r]=i;
}
printf("%lld\n",f[n]);
return 0;
}