[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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值