bzoj1911[Apio2010] 特别行动队
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1911
题目大意:
有n个数,分成连续的若干段,每段的分数为a*x^2+b*x+c(a,b,c是给出的常数),其中x为该段的各个数的和。求如何分才能使得各个段的分数的总和最大。
===============================================
题解:
斜率优化 这个方程很好写啊
设f[i]表示i为一个分段点且i之前的已经分好搞好了的最大值。
sum[]为前缀和
那么方程就是f[i]=f[j]+a*(sum[i]-sum[j-1])^2+b*(sum[i]-sum[j-1])+c
拆了移项得:2*a*sum[i]*sum[j-1]+f[i]=f[j]+a*sum[j-1]^2-b*sum[j-1]+(a*sum[i]^2+b*sum[i]+c)
虽然可能有点长..但是真的很好化(水)
还有,求最大值,所以维护上凸包哦~
代码~
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef long long LL; #define maxn 1010000 int q[maxn],l,r; LL f[maxn],sum[maxn],a,b,c; double Y(int j){return f[j]+a*sum[j]*sum[j]-b*sum[j];} double X(int j){return sum[j];} double slop(int j1,int j2){return (Y(j2)-Y(j1))/(X(j2)-X(j1));} int main() { //freopen("a.in","r",stdin); //freopen("a.out","w",stdout); int n,i;scanf("%d",&n); scanf("%lld%lld%lld",&a,&b,&c); sum[0]=0; for (i=1;i<=n;i++) { scanf("%lld",&sum[i]); sum[i]+=sum[i-1]; } memset(f,0,sizeof(f)); l=r=1;q[1]=0; for (i=1;i<=n;i++) { while (l<r && slop(q[l],q[l+1])>2*a*sum[i]) l++; int j=q[l]; f[i]=f[j]+a*(sum[i]-sum[j])*(sum[i]-sum[j])+b*(sum[i]-sum[j])+c; while (l<r && slop(q[r-1],q[r])<slop(q[r],i)) r--; q[++r]=i; }printf("%lld\n",f[n]); }