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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章