BZOJ 1911: [Apio2010]特别行动队 [斜率优化dp]

题意

给出一个常数a,b,c和数列{xn },将其分成若干段每一段至少有一个数,并且每一段将产生一个贡献为ax2+bx+c

找到一种分组方案使贡献和最大

解题报告

前两天写的没来得及写博客现在找不到推式子的草稿纸了…只好再推一遍…

斜率优化

设置状态fi 表示分到第i 个数,且以这个数为当前最后一组结尾的最大贡献和

转移时若有j<k ,则

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]+asum[j]2bsum[j](f[k]+asum[k]2bsum[k]))/(2a(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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章