先来化一下方差的式子:
[S_m^2=frac{1}{m} imes sum_{i=1}^m(a_i-frac{1}{m}sum_{j=1}^{m}a_j)^2\
=frac{1}{m} imes (sum_{i=1}^ma_i^2-sum_{i=1}^ma_i imes frac{2}{m}sum_{i=1}^ma_i+frac{(sum_{i=1}^ma_i)^2}{m})\
=frac{1}{m} imes (sum_{i=1}^m a_i^2-frac{(sum_{i=1}^ma_i)^2}{m})
]
乘一个 (m^2) 约成下面这样:
[m^2 imes S_m^2=msum_{i=1}^{m}a_i^2-(sum)^2
]
后面是个常量,不管,我们来最小化前面这个东西。
区间划分问题容易想到dp,这里还有个数的限制所以再加一维。可以得到我们的状态 (f[i][j]) 代表到第 (i) 个,分成 (j) 段。有转移方程 (f[i][j]=min{f[k][j-1]+(a[i]-a[k])^2}),(a) 是前缀和。
拆开和我们发现可以斜率优化,并且 (a) 是单调的,因为是求最小值所以用下凸壳。
#include <bits/stdc++.h>
using namespace std;
const int N = 3010;
int n, m;
int a[N], f[N][N];
int q[N];
double X(int i) {
return a[i];
}
double Y(int i, int j) {
return a[i] * a[i] + f[i][j - 1];
}
double slope(int i, int j, int k) {
return (Y(j, k) - Y(i, k)) / (X(j) - X(i));
}
int main() {
memset(f, 0x3f, sizeof(f));
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i], a[i] += a[i - 1];
for (int i = 1; i <= n; i++) f[i][1] = a[i] * a[i];
for (int j = 2; j <= m; j++) {
int head = 1, tail = 0;
q[++tail] = j - 1;
for (int i = j; i <= n; i++) {
f[i][j] = a[i] * a[i];
while (head < tail && slope(q[head], q[head + 1], j) <= 2.0 * a[i]) head++;
f[i][j] += Y(q[head], j) - 2 * a[i] * X(q[head]);
while (head < tail && slope(q[tail - 1], q[tail], j) >= slope(q[tail], i, j)) tail--;
q[++tail] = i;
}
}
cout << m * f[n][m] - a[n] * a[n];
return 0;
}