Problem
给出一个有 n n n 个数的序列 { a n } \{a_n\} {an},现要把它分成 m m m 段,设每段的权值为该段 a i a_i ai 之和,求出这 m m m 段的最小方差。
设方差是 v v v,可以证明, v × m 2 v\times m^2 v×m2 是一个整数。为了避免精度误差,输出结果时输出 v × m 2 v\times m^2 v×m2。
数据范围: 1 ≤ n ≤ 3000 1 \le n \le 3000 1≤n≤3000, ∑ a i ≤ 30000 \sum a_i\le30000 ∑ai≤30000。
Solution
这道题和序列分割是差不多的思路。
假设 b i b_i bi 为每段的权值,用 b ‾ \overline b b 表示平均数,我们要最小化的是这个式子:
∑ i = 1 m ( b i − b ‾ ) 2 m × m 2 \frac{\sum_{i=1}^m(b_i-\overline b)^2}{m}\times m^2 m∑i=1m(bi−b)2×m2
把平方拆开,就是:
m ( ∑ i = 1 m ( b i 2 + b ‾ 2 − 2 b i b ‾ ) ) m(\sum_{i=1}^m(b_i^2+\overline b^2-2b_i\overline b)) m(i=1∑m(bi2+b2−2bib))
那继续化简,并代入 b ‾ = ∑ i = 1 m b i m \overline b=\frac{\sum_{i=1}^mb_i}{m} b=m∑i=1mbi,不难得到(我就直接化到最后一步了):
m ∑ i = 1 m ( b i 2 ) − ( ∑ i = 1 m b i ) 2 m\sum_{i=1}^m(b_i^2)-(\sum_{i=1}^mb_i)^2 mi=1∑m(bi2)−(i=1∑mbi)2
后面那一块是定值,我们现在就要最小化 ∑ i = 1 m ( b i 2 ) \sum_{i=1}^m(b_i^2) ∑i=1m(bi2)。
设 F i , j F_{i,j} Fi,j 表示前 i i i 个数分成 j j j 段的最小值,设 S i S_i Si 为 a i a_i ai 的前缀和,那么有以下转移:
F i , k = min j = 1 i − 1 { F j , k − 1 + ( S i − S j ) 2 } F_{i,k}=\min_{j=1}^{i-1}\{F_{j,k-1}+(S_i-S_j)^2\} Fi,k=j=1mini−1{Fj,k−1+(Si−Sj)2}
这也可以滚动数组优化,设 F i , k F_{i,k} Fi,k 为 f i f_i fi, F j , k − 1 F_{j,k-1} Fj,k−1 为 g j g_j gj,即:
f i = min j = 1 i − 1 { g j + ( S i − S j ) 2 } f_i=\min_{j=1}^{i-1}\{g_j+(S_i-S_j)^2\} fi=j=1mini−1{gj+(Si−Sj)2}
取 k < j < i k<j<i k<j<i 且用 j j j 转移比 k k k 优,则有:
g j + ( S i − S j ) 2 < g k + ( S i − S k ) 2 g_j+(S_i-S_j)^2<g_k+(S_i-S_k)^2 gj+(Si−Sj)2<gk+(Si−Sk)2
化简之后就是:
( g j + S j 2 ) − ( g k + S k 2 ) S j − S k < 2 S i \frac{(g_j+S_j^2)-(g_k+S_k^2)}{S_j-S_k}<2S_i Sj−Sk(gj+Sj2)−(gk+Sk2)<2Si
于是维护一个下凸壳然后斜率优化即可。
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 3005
#define ll long long
using namespace std;
int n,m,Q[N];
ll f[N],g[N],S[N];
ll Squ(ll x) {return x*x;}
ll ordi(int x) {return g[x]+S[x]*S[x];}
double slope(int x,int y) {return 1.0*(ordi(y)-ordi(x))/(S[y]-S[x]);}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,x;i<=n;++i){
scanf("%d",&x),S[i]=S[i-1]+x;
}
for(int i=1;i<=n;++i) g[i]=S[i]*S[i];
for(int j=1;j<m;++j){
int l=0,r=0;
for(int i=1;i<=n;++i){
while(l<r&&slope(Q[l],Q[l+1])<=2*S[i]) l++;
f[i]=g[Q[l]]+Squ(S[i]-S[Q[l]]);
while(l<r&&slope(Q[r-1],Q[r])>=slope(Q[r],i)) r--;
Q[++r]=i;
}
memcpy(g,f,sizeof(g));
}
printf("%lld",f[n]*m-S[n]*S[n]);
return 0;
}