传送门

听亲妈聊耽美和魔道祖师是种什么体验

令$S_i$为分成$k+1$段后第$i$段的和。

考虑任意的第$i$段和第$j$段($i\neq j$)之间的贡献,一定会存在某次分割使它们分开,产生$S_iS_j$的贡献,之后两者再无关联。

所以答案为$\sum\limits_{i=1}^{k+1}\sum\limits_{j=i+1}^{k+1}S_iS_j$,与分割顺序无关。

把这个式子变一变:$\dfrac{\left(\sum\limits_{i=1}^{k+1}S_i\right)^2-\sum\limits_{i=1}^{k+1}S_i^2}{2}$

$\left(\sum\limits_{i=1}^{k+1}S_i\right)^2$就是整个序列的和的平方,我们的任务就成了最小化$\sum\limits_{i=1}^{k+1}S_i^2$。

设$s_i$为数列前缀和,$f(i,j)$为前$i$个数分成$j$段的最小代价。

显然,$f(i,k)=\min\limits_{j<i}\{f(j,k-1)+(s_i-s_j)^2\}$

变个形:$f(j,k-1)+s_j^2=f(i,k)+2s_is_j-s_i^2$

然后就能斜率优化了。有一个坑点是序列可能有$0$,所以斜率可能不存在。

复杂度$O(nk)$。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>

#define maxn 100005
#define inf 0x3f3f3f3f

using namespace std;

inline int read(){
    int x=0,y=0;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')y=1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return y?-x:x;
}
long long f[maxn][2],y[maxn];
int x[maxn],g[maxn][205],p[maxn],s[maxn],head,tail;
inline void clear(){head=1,tail=0;}
inline void push(int xx,long long yy,int pos){
    while(head<tail&&xx!=x[tail]&&(x[tail]==x[tail-1]||(double)(y[tail]-y[tail-1])/(x[tail]-x[tail-1])>(double)(yy-y[tail])/(xx-x[tail])))--tail;
    x[++tail]=xx,y[tail]=yy,p[tail]=pos;
}
inline int front(int k){
    while(head<tail&&(double)(y[head+1]-y[head])<=1ll*k*(x[head+1]-x[head]))++head;
    return p[head];
}
inline long long sqr(int x){return 1ll*x*x;}
int main(){
    int n=read(),k=read()+1;
    for(register int i=1;i<=n;++i)f[i][1]=sqr(s[i]=s[i-1]+read());
    for(register int j=2;j<=k;++j){
        clear(),push(s[j-1],f[j-1][j&1^1]+sqr(s[j-1]),j-1);
        for(register int i=j;i<=n;++i){
            int J=g[i][j]=front(s[i]<<1);
            f[i][j&1]=f[J][j&1^1]+sqr(s[i]-s[J]);
            push(s[i],f[i][j&1^1]+sqr(s[i]),i);
        }
    }
    printf("%lld\n",1ll*s[n]*s[n]-f[n][k&1]>>1);
    for(register int j=k,i=n;j>1;--j)printf("%d ",i=g[i][j]);
    pn;
}