题目大意:将n个数分成m段,每段求和,求这m段和的方差的最小值*m^2
由方差公式 可进一步推导到
(否则没法优化 = =),所以我们只要求出F[i][j]代表前j个数分成i段,这i段的和的平方的最小值即可,这样这就是一个愉快的经典问题了。
显然
f(i,j)=max{f(i-1,k)+(s[j]-s[k])^2} 令ans(k)=f(i-1,k)+(s[j]-s[k])^2=f(i-1,k)+s[j]^2-2*s[j]*s[k]+s[k]^2
则有f(i-1,k)+s[k]^2=2*s[j]*s[k]+ans(k)-s[j]^2
令y=f(i-1,k)+s[k]^2,a=2*s[j],x=s[k],b=ans[k]-s[j]^2,那么
显然y=ax+b且a是定值,即斜率是定值。并且x和y单调递增,因此可以证明,如果我们将x和y看做二维上的点,所有答案的点都在当前的凸壳上,所以维护一个凸壳就好了。
#include"bits/stdc++.h"
using namespace std;
typedef long long ll;
const int N=3005;
int d[N],s[N],n,m,q[N],l,r;
ll f[N][N];
#define sqr(x) ((x)*(x))
inline double slope(int i,int k,int p)
{return (f[i-1][k]-f[i-1][p]+sqr(s[k])-sqr(s[p]))*1.0/(s[k]-s[p]);}
int main(){
scanf("%d%d",&n,&m);int i,j;
for(i=1;i<=n;i++)scanf("%d",&d[i]);
for(i=1;i<=n;i++)s[i]=s[i-1]+d[i];
memset(f,63,sizeof(f));f[0][0]=0;
for(i=1;i<=m;i++){
for(j=l=r=1;j<=n;j++){
while(l<r&&slope(i,q[l],q[l+1])<s[j]*2)l++;
if(i<=j)f[i][j]=f[i-1][q[l]]+sqr(s[j]-s[q[l]]);
while(l<r&&slope(i,q[r-1],q[r])>slope(i,q[r],j))r--;
q[++r]=j;
}
}
printf("%lld\n",(ll)f[m][n]*m-s[n]*s[n]);
return 0;
}