[APIO2014]序列分割

你正在玩一個關於長度爲 n 的非負整數序列的遊戲。這個遊戲中你需要把序列分成 k + 1 個非空的塊。爲了得到 k + 1 塊,你需要重複下面的操作 k 次:
選擇一個有超過一個元素的塊(初始時你只有一塊,即整個序列)
選擇兩個相鄰元素把這個塊從中間分開,得到兩個非空的塊。
每次操作後你將獲得那兩個新產生的塊的元素和的乘積的分數。你想要最大化最後的總得分。

滿足n<=100000,k<=200.

設有元素A B C D

(A+B)*(C+D)+(C*D)=AC+AD+BC+BD+CD=D(A+B+C)+C(B+A)

由此推廣得到 元素切割順序不影響得分情況
即可得到樸素DP 截止前i個元素被分成了j塊的最大得分爲f[i][j]
記錄前綴和爲s[i] f[i][j]=max(f[k][j-1]+(s[i]-s[j])*s[j] );
複雜度O(n²k)
開滾動數組優化空間。記錄g[i]爲分成j-1個塊的最大得分。考慮斜率優化,推一推式子,轉移時記錄位置然後直接寫就好了。
calc別整除。。別整除。。

#include<bits/stdc++.h>
using namespace std;

#define ll long long

ll f[200005],q,sum[200005],n,a[200005],g[200005],dque[200005],ans;

inline ll read(){
    char c=getchar();ll x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

int to[205][120005];

inline ll X(ll x){
    return -sum[x];
}

inline ll Y(ll x){
    return g[x]-sum[x]*sum[x];
}

// f[i][j]=f[k][j-1]+(sum[k])*(sum[i]-sum[k])
// f[i][j]-sum[k]*sum[i]=f[k][j-1]-sum[k]^2
//  b          kx   =   y            

inline double calc(ll x,ll y){
    if(X(y)-X(x)==0)return -1e18;
    return (double)(Y(y)-Y(x))/(X(y)-X(x));
}

int sta[400005],top=0;

int main(){
    scanf("%lld%lld",&n,&q);
    for(int i=1;i<=n;i++){
        a[i]=read();
    }
    for(int i=1;i<=n;i++){
        sum[i]=sum[i-1]+a[i];
    }
    for(int k=1;k<=q;k++){  
        ll l=0,r=0;
        dque[0]=0;
        for(int i=k;i<=n;i++){
            while(l<r&&calc(dque[l],dque[l+1])<=sum[i])l++;
            int j=dque[l];
            f[i]=g[j]+sum[j]*sum[i]-sum[j]*sum[j];
            to[k][i]=j;
            while(l<r&&calc(dque[r-1],dque[r])>=calc(dque[r],i))r--;
            dque[++r]=i;
        }
        for(int i=k;i<=n;i++)g[i]=f[i];
    }

    printf("%lld\n",f[n]);

    for(int i=to[q][n];i&&q;i=to[--q][i]){
        sta[++top]=i;
    }
    for(int i=top;i>=1;i--){
        printf("%d ",sta[i]);
    }
    return 0;
}
//10 5
//7 9 5 4 6 8 2 3 1
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章