题目描述
Pine 开始了从 (S) 地到 (T) 地的征途。
从 (S) 地到 (T) 地的路可以划分成 (h) 段,相邻两段路的分界点设有休息站。
Pine 计划用 (m) 天到达 (T) 地。除第 (n) 天外,每一天晚上 Pine 都必须在休息站过夜。所以,一段路必须在同一天中走完。
Pine 希望每一天走的路长度尽可能相近,所以他希望每一天走的路的长度的方差尽可能小。
帮助 Pine 求出最小方差是多少。
设方差是 (v) ,可以证明,(v*m^2) 是一个整数。为了避免精度误差,输出结果时输出 (v*m^2)。
输入格式
第一行两个数 (n) 、(m) 。
第二行 (n) 个数,表示 (n) 段路的长度。
题解
CCF不考DP了(悲)
假设有一个序列 (a_1sim a_n) ,平均数是 (v) ,那么它的方差就是 (dfrac{sumlimits_{i=1}^{n}(a_i)^2}{n}-v^2)
所以原问题就变成:将 (n) 个数分为 (m) 段,假设第 (i) 段内的所有数之和为 (b_i) ,求 (sumlimits_{i=1}^{m} (b_i)^2) 的最小值
设 (f_{i,j}) 表示将前 (j) 个数分成 (i) 段的最小平方和
容易得到转移方程: (f_{i,j} = minlimits_{0le k<j} (f_{i-1,k} + (S_j-S_k)^2)) , (S) 表示前缀和
变换一下得到 (f_{i-1,k}+(S_k)^2=2*S_j*S_k+(f_{i,j}-(S_j)^2))
这样就可以进行斜率优化,维护 (f_{i-1}) 的凸壳来转移出 (f_i)
由于 (S) 数组单调递增,所以直接用单调队列维护下凸壳即可
时间复杂度 (O(nm))
代码
#include <bits/stdc++.h>
#define N 5005
using namespace std;
typedef long long ll;
template<typename T>
inline void read(T &num) {
T x = 0, f = 1; char ch = getchar();
for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') f = -1;
for (; ch <= '9' && ch >= '0'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ '0');
num = x * f;
}
int n, m, head, tail, q[N];
ll a[N], f[2][N], x[N], y[N];
inline double slope(int p, int q) {
if (x[p] == x[q]) return 1e15;
else return 1.0 * (y[p] - y[q]) / (x[p] - x[q]);
}
int main() {
read(n); read(m);
for (int i = 1; i <= n; i++) {
read(a[i]); a[i] += a[i-1];
}
int o = 0;
for (int i = 1; i <= m; i++) {
o = i & 1;
head = 1; tail = 0;
for (int j = 0; j <= n; j++) {
if (i > 1 || j == 0) {
while (head < tail && slope(q[tail-1], q[tail]) > slope(q[tail], j)) tail--;
q[++tail] = j;
}
while (head < tail && slope(q[head], q[head+1]) < (double)a[j]) head++;
int k = q[head];
f[o][j] = f[!o][k] + (a[j]-a[k]) * (a[j]-a[k]);
}
for (int j = 0; j <= n; j++) {
f[!o][j] = 0;
x[j] = 2 * a[j]; y[j] = f[o][j] + a[j] * a[j];
}
}
printf("%lld
", f[m&1][n] * m - a[n] * a[n]);
return 0;
}