Description

Pine开始了从S地到T地的征途。
从S地到T地的路可以划分成n段,相邻两段路的分界点设有休息站。
Pine计划用m天到达T地。除第m天外,每一天晚上Pine都必须在休息站过夜。所以,一段路必须在同一天中走完。
Pine希望每一天走的路长度尽可能相近,所以他希望每一天走的路的长度的方差尽可能小。
帮助Pine求出最小方差是多少。
设方差是v,可以证明,v×m^2是一个整数。为了避免精度误差,输出结果时输出v×m^2。
Input

第一行两个数 n、m。
第二行 n 个数,表示 n 段路的长度
Output

一个数,最小方差乘以 m^2 后的值

Sample Input

5 2

1 2 5 8 6
Sample Output

36
HINT

1≤n≤3000,保证从 S 到 T 的总路程不超过 30000


​​​传送门​​​
假设说用S(i,j)表示i~j内的数字和,sum表示S(1,n),
然后设分成的m段是:(Li,Ri),其中Li=R(i-1)+1
那么我们知道答案是:

min{1m∗∑[S(Li,Ri)−avg]2}∗m2avg=summ


我们把m2乘进去,那么马上得到了:

min{m∗∑(S(Li,Ri)−summ)2}


再化简,并且把平方项展开:

min{∑(m∗S(Li,Ri)2−2∗sum∗S(Li,Ri)+(summ)2∗m}1<=i<=m


由于i有m个,所以最后的那一项到最后也是整数,而且可以提取出来。

所以经过化简,我们得到了最终答案的表达式:

min{∑(m∗S(Li,Ri)2−2∗sum∗S(Li,Ri)}+sum21<=i<=m


为了维护前面min里面的部分的最小值,设计一个简单的dp:

f[i][j]表示前i个,分了j块的最小花费。

f[i][j]=min{f[k][j]+calc(k+1,i)}

发现这是一个O(N^2*M)的算法,那么就可以考虑斜率优化啦

……为了方便就把j,i互换吧,因为斜率优化是在i维上取最优值。

f[j][i],,

斜率推导过程就不说了。。= =

现在微辣方便就用Si表示1~i这个前缀和,

而且f[i],j维先不写出来吧!

反正斜率推出来是酱紫的:

m∗Sj2−m∗Sk2+2∗sum∗Sj−2∗sum∗Sk+f[j]−f[k]2∗m∗Sj−2∗m∗Skj<k<i,j比k优


那么每次维护上一层的头,这一层的尾,就可以了。。

不卡精度,直接维护斜率就好了。


#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int
N=3005;
const ll
inf=2000000000LL;
int n,m,head[2],tail[2],Q[2][N];
ll sum[N],f[N][N];
ll calc(int i,int j,int k){
return f[j-1][k]+(ll)m*(sum[i]-sum[k])*(sum[i]-sum[k])-2LL*sum[n]*(sum[i]-sum[k]);
}
double xl(int o,int j,int k){
ll t1=sum[j]*sum[j]*m-sum[k]*sum[k]*m+sum[n]*sum[j]*2LL-sum[n]*sum[k]*2LL+f[o][j]-f[o][k],
t2=2LL*m*(sum[j]-sum[k]);
return (double)t1/(double)t2;
}
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++){
scanf("%lld",&sum[i]);
sum[i]+=sum[i-1];
}
head[0]=tail[0]=0,Q[0][0]=0;
for (int j=1;j<=m;j++){
int now=j&1,pre=now^1;
head[now]=tail[now]=0;
for (int i=1;i<=n;i++){
while (head[pre]<tail[pre] &&
calc(i,j,Q[pre][head[pre]])>
calc(i,j,Q[pre][head[pre]+1])) head[pre]++;
f[j][i]=calc(i,j,Q[pre][head[pre]]);
while (head[now]<tail[now] &&
xl(j,Q[now][tail[now]-1],Q[now][tail[now]])>
xl(j,Q[now][tail[now]],i)) tail[now]--;
Q[now][++tail[now]]=i;
}
}
printf("%lld\n",f[m][n]+sum[n]*sum[n]);
return 0;