[APIO2014]序列分割

来源:互联网 发布:达内java培训班有用吗 编辑:程序博客网 时间:2024/06/05 19:41

你正在玩一个关于长度为 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 longint 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;}