[APIO2014]序列分割

你正在玩一个关于长度为 nn 的非负整数序列的游戏。这个游戏中你需要把序列分成 k+1k+1 个非空的块。为了得到 k+1k+1 块,你需要重复下面的操作 kk 次:

选择一个有超过一个元素的块(初始时你只有一块,即整个序列)
选择两个相邻元素把这个块从中间分开,得到两个非空的块。

每次操作后你将获得那两个新产生的块的元素和的乘积的分数。你想要最大化最后的总得分。
输入格式

第一行包含两个整数 nn 和 kk。保证 k+1≤nk+1≤n。

第二行包含 nn 个非负整数 a1,a2,…,ana1,a2,…,an (0≤ai≤104)(0≤ai≤104),表示前文所述的序列。
输出格式

第一行输出你能获得的最大总得分。

第二行输出 kk 个介于 11 到 n−1n−1 之间的整数,表示为了使得总得分最大,你每次操作中分开两个块的位置。第 ii 个整数 sisi 表示第 ii 次操作将在 sisi 和 si+1si+1 之间把块分开。

如果有多种方案使得总得分最大,输出任意一种方案即可。
样例一
input

7 3
4 1 3 4 0 2 3

output

108
1 3 5

explanation

你可以通过下面这些操作获得 108108 分:

初始时你有一块 (4,1,3,4,0,2,3)(4,1,3,4,0,2,3)。在第 11 个元素后面分开,获得 4×(1+3+4+0+2+3)=524×(1+3+4+0+2+3)=52 分。
你现在有两块 (4),(1,3,4,0,2,3)(4),(1,3,4,0,2,3)。在第 33 个元素后面分开,获得 (1+3)×(4+0+2+3)=36(1+3)×(4+0+2+3)=36 分。
你现在有三块 (4),(1,3),(4,0,2,3)(4),(1,3),(4,0,2,3)。在第 55 个元素后面分开,获得 (4+0)×(2+3)=20(4+0)×(2+3)=20 分。

所以,经过这些操作后你可以获得四块 (4),(1,3),(4,0),(2,3)(4),(1,3),(4,0),(2,3) 并获得 52+36+20=10852+36+20=108 分。
限制与约定

第一个子任务共 11 分,满足 1≤k

/*
50分的暴力: 复杂度: O(kn^2)
首先要发现一个性质: 只要选定了切的位置,不论什么顺序切,答案都是一样
例如: 把序列切两次,每段的和分为 x1 x2 x3,
先在1,2间切的得分: x1*(x2+x3)+x2*x3 = x1*x2+x1*x3+x2*x3
先在1,3间切的得分: x3*(x1+x2)+x1*x2 = x1*x3+x1*x2+x1*x2
所以就可以
用dp(i,j) 表示前j个数切i次的总得分,sum是前缀和
dp(i,j) = max(dp(i-1, k) + (sum(j)-sum(k))*sum(k));
k是枚举<j的区间里的一个切点
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
using namespace std;
const int maxx=1e5+5;
#define For(i,a,b) for(register int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(register int i=(a);i>=(b);--i)
#define LL long long
int read(){
    char x=getchar(); int u=0;
    while(!isdigit(x)) x=getchar();
    while(isdigit(x)) u=(u<<3)+(u<<1)+(x^48), x=getchar();
    return u;
}
LL sum[maxx],dp[201][maxx],to[201][maxx],n,k;
int main(){
#ifndef ONLINE_JUDGE
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
#endif
    n=read(); k=read();
    For(i,1,n)
        sum[i]=sum[i-1]+read();
    For(i,1,n)
        For(j,1,i-1)
            For(p,1,k)
                if(dp[p][i]<=dp[p-1][j]+sum[j]*(sum[i]-sum[j])){
                    dp[p][i]=dp[p-1][j]+sum[j]*(sum[i]-sum[j]);
                    to[p][i]=j;
                }
    printf("%lld\n",dp[k][n]);
    int i=n;
    Rep(j,k,1){
        i=to[j][i];
        printf("%d ",i);
    }


    return 0;
}
/*
在50分的暴力基础上,因为原式大概为
    dp(i)=max(dp(k) + sum(i) * sum(k) - sum(k)^2);
可以斜率优化
    假设 j<k 即可以从k转移
        sum(i) > [ sum(j)^2-sum(k)^2 + dp(k) - dp(j) ] / (sum(j) -sum(k);
输出方案时,只需要记录每次由谁转移
洛谷上只开了128M,uoj上开了256M,所以洛谷上会MLE
幸好dp(i,j) =max(dp(i-1, k) +sum(...))
每次只由上一层转移过来,所以可以开滚动数组
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
using namespace std;
const int maxx=1e5+1;
#define For(i,a,b) for(register int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(register int i=(a);i>=(b);--i)
#define LL long long
#define sqr(x) ((x)*(x))
int read(){
    char x=getchar(); int u=0;
    while(!isdigit(x)) x=getchar();
    while(isdigit(x)) u=(u<<3)+(u<<1)+(x^48), x=getchar();
    return u;
}
LL sum[maxx],dp[2][maxx],n,k,p;
int to[201][maxx];
int q[maxx],l,r;
bool type=0;
double slope(int x,int y){          // 斜率
    if(sum[x]==sum[y]) return -1e18;
    return (double)(sqr(sum[x]) - sqr(sum[y])-dp[type^1][x] + dp[type^1][y]) / (double) (sum[x]-sum[y]);
}
int main(){
#ifndef ONLINE_JUDGE
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
#endif
    n=read(); k=read();
    For(i,1,n)
        sum[i]=sum[i-1]+read();
    for(p=1; p<=k; ++p){
        l=r=0; type^=1;
        For(i,1,n){
            while(l<r && slope(q[l],q[l+1]) <= sum[i]) ++l;
            dp[type][i]=dp[type^1][q[l]]+sum[q[l]]*(sum[i]-sum[q[l]]);
            to[p][i]=q[l];
            while(l<r && slope(q[r-1],q[r]) >= slope(q[r],i)) --r;
            q[++r]=i;
        }
    }
    printf("%lld\n",dp[type][n]);
    int i=n;
    Rep(j,k,1){
        i=to[j][i];
        printf("%d ",i);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值