令f[i][j]表示走i段当前在j点的最优答案。首先所有数都*m,最后再/m,令ave表示平均数,显然有f[i][j]=min{f[i-1][k]+(s[i]-s[k]-ave)^2}
很显然的斜率优化。对于i,由j>k,f[i-1][j]+(s[i]-s[j]-ave)^2<=f[i-1][k]+(s[i]-s[k]-ave)^2,化简得到(h[j]-h[k])/2/(s[j]-s[k])<=s[i],其中h[j]=f[i-1][j]+s[j]^2+2*ave*s[j],然后单调队列维护一下即可。
AC代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
#define sqr(x) (ll)(x)*(x)
#define inf 100000000000000000LL
#define N 3005
using namespace std;
int n,m,t,a[N],s[N],q[N]; ll f[2][N],h[N];
double getk(int x,int y){
return (double)(h[y]-h[x])/(s[y]-s[x])/2;
}
int main(){
scanf("%d%d",&n,&m); int i,j,head,tail;
for (i=1; i<=n; i++){
scanf("%d",&a[i]); t+=a[i];
a[i]*=m; s[i]=s[i-1]+a[i];
}
for (i=1; i<=n; i++) f[0][i]=sqr(s[i]-t); f[0][0]=inf;
int now=0,last;
for (i=2; i<=m; i++){
last=now; now^=1;
for (j=0; j<=n; j++) f[now][j]=inf;
head=tail=1; q[1]=0; h[0]=f[last][0];
for (j=1; j<=n; j++){
while (head<tail && getk(q[head],q[head+1])<=s[j]) head++;
f[now][j]=f[last][q[head]]+sqr(s[j]-s[q[head]]-t);
h[j]=f[last][j]+sqr(s[j])+(ll)s[j]*t*2;
while (head<tail && getk(q[tail],j)<getk(q[tail-1],q[tail])) tail--;
q[++tail]=j;
}
}
printf("%lld\n",f[now][n]/m);
return 0;
}
by lych
2016.4.23