题目描述

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_1\sim a_n\) ,平均数是 \(v\) ,那么它的方差就是 \(\dfrac{\sum\limits_{i=1}^{n}(a_i)^2}{n}-v^2\)

所以原问题就变成:将 \(n\) 个数分为 \(m\) 段,假设第 \(i\) 段内的所有数之和为 \(b_i\) ,求 \(\sum\limits_{i=1}^{m} (b_i)^2\) 的最小值

设 \(f_{i,j}\) 表示将前 \(j\) 个数分成 \(i\) 段的最小平方和

容易得到转移方程: \(f_{i,j} = \min\limits_{0\le 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\n", f[m&1][n] * m - a[n] * a[n]);
	return 0;
}

相关文章:

  • 2021-10-04
  • 2021-06-08
  • 2021-06-21
  • 2021-09-29
  • 2021-11-13
  • 2021-09-18
  • 2021-06-09
  • 2022-02-13
猜你喜欢
  • 2022-02-28
  • 2022-01-21
  • 2021-10-02
  • 2021-10-22
  • 2021-10-14
相关资源
相似解决方案