正题
题目链接:https://loj.ac/problem/2035
题目大意
nnn个数字分成mmm段,要求方差最小。
解题思路
首先方差的公式∑i=1n(xi−∣x∣)2\sum_{i=1}^n(x_i-|x|)^2i=1∑n(xi−∣x∣)2
其中∣x∣|x|∣x∣是不变的,定义w=∣x∣w=|x|w=∣x∣
设fi,jf_{i,j}fi,j表示已经分到第iii段,到第jjj个时的最小方差和。
做前缀和si=∑j=1iais_i=\sum_{j=1}^ia_isi=∑j=1iai
之后有fk,i=min{fk−1,j+(si−sj)2+w2−2(si−sj)w}f_{k,i}=min\{f_{k-1,j}+(s_i-s_j)^2+w^2-2(s_i-s_j)w\}fk,i=min{fk−1,j+(si−sj)2+w2−2(si−sj)w}
去掉minminmin拆括号
fk,i=fk−1,j+si2−2sisj+sj2+w2−2siw+sjwf_{k,i}=f_{k-1,j}+s_i^2-2s_is_j+s_j^2+w^2-2s_iw+s_jwfk,i=fk−1,j+si2−2sisj+sj2+w2−2siw+sjw
fk,i−si2+siw+2sisj−2sjw=fk−1,j+sj2f_{k,i}-s_i^2+s_iw+2s_is_j-2s_jw=f_{k-1,j}+s_j^2fk,i−si2+siw+2sisj−2sjw=fk−1,j+sj2
求fk,if_{k,i}fk,i最小就是fk,i−si2+siwf_{k,i}-s_i^2+s_iwfk,i−si2+siw最小,后为了方便
定义F=fk,i−si2+siwF=f_{k,i}-s_i^2+s_iwF=fk,i−si2+siw
F+2(si−w)sj=fk−1,j+sj2F+2(s_i-w)s_j=f_{k-1,j}+s_j^2F+2(si−w)sj=fk−1,j+sj2
然后有若干个决策点(sj,fk−1,j+sj2)(s_j,f_{k-1,j}+s_j^2)(sj,fk−1,j+sj2)
每次有一条直线y=2(si−w)x+Fy=2(s_i-w)x+Fy=2(si−w)x+F经过某个决策点要求FFF最小
显然因为si−ws_i-wsi−w的单调性和sjs_jsj的单调性我们可以使用单调队列维护一个下凸壳。
时间复杂度O(nm)O(nm)O(nm)
codecodecode
#include<cstdio>
#include<cstring>
#include<algorithm>
#define pow2(x) ((x)*(x))
using namespace std;
const int N=3100;
struct node{double x,y;int num;
}q[N];
int n,m;
double s[N],f[N][N];
double slope(node x,node y)
{return (y.y-x.y)/(y.x-x.x);}
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)scanf("%lf",&s[i]),s[i]=s[i]*m+s[i-1];double w=s[n]/m;for(int i=1;i<=n;i++)f[1][i]=pow2(s[i]-w);for(int k=2;k<=m;k++){int head=1,tail=1;q[1]=(node){s[k-1],f[k-1][k-1]+pow2(s[k-1]),k-1};for(int i=k;i<=n;i++){int z=2*(s[i]-w);while(head<tail&&slope(q[head],q[head+1])<z)head++;int p=q[head].num;f[k][i]=f[k-1][p]+pow2(s[i]-s[p]-w);node po=(node){s[i],f[k-1][i]+pow2(s[i]),i};while(head<tail&&slope(po,q[tail])<slope(q[tail-1],q[tail]))tail--;q[++tail]=po;}}printf("%.0lf",f[m][n]/m);
}