TonyYin's Blog

Back

题意#

给定一个长度为 nn 的序列 a1,a2,ana_1, a_2\cdots, a_n,现在要把它划分成 kk 段,问每段平均数的最小值最大能是多少。

精度上,要求误差 0.001\leq 0.001.

1kn2×1051\leq k\leq n\leq 2\times 10^51ai1001\leq a_i\leq 100.

分析#

要让最小值最大,想到二分答案,但 check\rm{check} 部分不是很好想。

容易想到时间复杂度较劣的DP做法。

暴力DP#

fi,jf_{i, j} 表示,处理完前 ii 个数,分成了 jj 段,每段平均数最小值的最大可能值

预处理前缀和 si=j=1iais_i=\sum_{j=1}^{i}{a_i},则有转移方程:

fi,j=maxk=1i{min{fk,j1,siskik}}f_{i, j}=\max_{k=1}^{i}\{\min\{{f_{k, j-1}, \frac{s_i-s_k}{i-k}}\}\}

表示 [1,k][k+1,i][1,i][1, k]\cup[k+1, i] \rightarrow [1, i] 的过程,要和新加入的 [k+1,i][k+1, i] 的平均值取 min\min.

这样 DP 的时间复杂度为 O(n3)\mathcal{O}(n^3),不是很容易继续优化。

二分答案#

假设二分出了一个值 xx,需要 check\rm{check} 其是否可能作为答案。

我们把序列中的每个数都减去 xx 后,一段的平均数 x\geq x 当且仅当这段的元素和 0\geq 0.

在每次 check\rm{check} 时,先算出数列 bi=aixb_i=a_i-x,再对 bib_i 计算前缀和 sis_i

若序列可以分为 kk 个平均值 x\geq x 的段,则说明在序列 ss 上,以一个 >0>0 的数为开头,且以 sns_n 为结尾的最长不下降子序列长度 k\geq k.

于是问题变成:如何快速求这样的最长不下降子序列。

在求出最长不下降子序列长度之后,check\rm{check} 操作就结束了。

最长不下降子序列#

求序列 ss 中,以非负整数开头,以 sns_n 结尾的最长不下降子序列。

fif_i 表示以 ii 结尾的最长不下降子序列长度,转移显然:

fi=maxj<i,0<aj<ai(fj+1)f_i=\max_{j<i, 0<a_j<a_i}(f_j+1)

暴力做是 O(n2)\mathcal{O}(n^2) 的,可以用树状数组优化到 O(nlog2n)\mathcal{O}(n\log_2 n).

先把点按照权值排序,树状数组以序列原本的下标为下标。

在按权值排序后的序列顺序递推,每次利用树状数组求前缀最大值即可。

代码#

实现的时候先整体 ×10000\times 10000,用整数计算。不过也可以直接用小数算。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f;
int n, k, a[MAXN];
class BIT{
	private:
		int c[MAXN], N = 2e5;
	public:
		void init(){ memset(c, 0xc0, sizeof(c)); }
		inline int lowbit(int x) { return x & (-x); }
		inline void update(int x, int w) {
			for(; x <= N; x += lowbit(x)) c[x] = max(c[x], w);
		}
		inline int query(int x) {
			int ret = -inf;
			for(; x; x -= lowbit(x)) ret = max(ret, c[x]);
			return ret;
		}
} Bit;
int pos[MAXN];
pair<int, int> s[MAXN];
bool check(int mid) {
	int cnt = 0; Bit.init();
	s[0] = make_pair(0, 0);
	for(int i = 1; i <= n; i++) {
		s[i].first = s[i - 1].first + a[i] - mid;
		s[i].second = i;
	}
	sort(s, s + n + 1);
	pos[s[0].second] = ++cnt;
	for(int i = 1; i <= n; i++) {
		if(s[i].first != s[i - 1].first) ++cnt;
		pos[s[i].second] = cnt;
	}
	//求:每个元素都>=0的最长上升子序列长度
	Bit.update(pos[0], 0);
	for(int i = 1; i < n; i++) {
		int ret = Bit.query(pos[i] - 1);
		Bit.update(pos[i], ret + 1);
	}
	return (Bit.query(pos[n] - 1) + 1 >= k);
}
signed main() {
	ios::sync_with_stdio(false);
	cin >> n >> k;
	for(int i = 1; i <= n; i++) cin >> a[i];
	for(int i = 1; i <= n; i++) a[i] *= 10000;
	int l = 0, r = 100 * 10000, mid, ans;
	while(l <= r) {
		mid = (l + r) >> 1;
		if(check(mid)) {
			ans = mid;
			l = mid + 1;
		} else r = mid - 1;
	}
	cout << ans / 10000.0 << endl;
 	return 0;
}
cpp
【ZROI–21noip赛前–Day15】套路题
https://www.tonyyin.top/blog/oi-solution/zroi2141
Author TonyYin
Published at November 9, 2021
Comment seems to stuck. Try to refresh?✨