传送门biu~
可以发现一个序列先切和后切获得的得分是一样的。
设
sumi
为初始序列的前缀和,设状态
fi,k
代表在i处分割、已经分割了k次的最大得分,那么
fi,k=((sumi−sumj)∗sumj+fj,k−1)max
考虑斜率优化。设P为f_{i,k}一个可行的值。则
P=sumi∗sumj−sum2j+fj,k−1
,设
x=sumj
,
y=sum2j−fj,k−1
,那么式子就变为了
y=sumi∗x−P
因为斜率
sumi
是定值,想让
−P
(即截距)最小,可以发现所求点在一个下凸包上。维护下凸包即可,时间复杂度O(k*n)。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
struct point{
LL x,y;
double slope;
point(LL _=0,LL __=0){
x=_;
y=__;
slope=0.0;
}
}stac[100005];
int head=1,tail=1;
int n,k;
int a[100005];
LL sum[100005],f[100005][2];
inline double get_slope(point x,point y){
if(x.x==y.x) return 1e9*(y.y>x.y?1:-1);
return (1.0*x.y-y.y)/(x.x-y.x);
}
void insert(point a){
double s=0;
while(head<tail){
s=get_slope(stac[tail-1],a);
if(stac[tail-1].slope>s) --tail;
else break;
}
stac[tail]=a;
stac[tail].slope=s;
++tail;
}
point get_ans(double x){
while(head!=tail-1){
if(stac[head+1].slope<x) ++head;
else break;
}
return stac[head];
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i) scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
for(int i=2;i<=k+1;++i){
head=tail=1;
for(int j=2;j<=n;++j){
insert(point(sum[j-1],sum[j-1]*sum[j-1]-f[j-1][~i&1]));
point t=get_ans(sum[j]);
f[j][i&1]=sum[j]*t.x-t.y;
}
}
printf("%lld",f[n][~k&1]);
return 0;
}