Codeforces Round #768 (Div. 1)

比赛链接:

https://codeforces.com/contest/1630

B. Range and Partition

题目大意:

给定一个序列 \(a\),取一个范围 [x, y],将序列分成 \(k\) 段,每一段中在 [x, y] 这个范围中的数要严格大于不在该范围中的数,求使 \(y - x\) 最小的划分序列的方法。

思路:

设一个序列 \(A\),若 x <= \(a_i\) <= y,则 \(A_i\) = 1,否则等于 -1,对 \(A\) 求一个前缀和,记为序列 \(sum\),显然,我们要实现 \(sum_n\) >= k,转化一下。
\(sum_n = \sum_{i = 1}^n b_i = \sum_{i = 1}^n (-1 + 2 * [x <= a_i <= y]) >= k\),即 \(\sum_{i = 1}^n [x <= a_i <= y] >= \lceil \frac{k + n}{2} \rceil\),那么我们就可以遍历 \(x\),然后去找一个最小的 \(y - x\)。
所以我们可以 \(sort\) 一下序列 \(a\),然后找到最小的区间,接着去寻找方案。
寻找方案的过程可以通过序列 \(b\) 和 \(sum\),只要当在范围中的数大于不在范围中的数时,就分段,即该区间的和大于 0,所以该区间的 sum 等于上一个区间的 sum 加 1,这样分出的区间全满足条件。
如果区间超过了 \(k\),只需要将后面多的区间全部合并,单个区间已经满足了条件,合并的区间也会满足条件。

代码:

#include <bits/stdc++.h>
using namespace std;
#define all(x) (x).begin(), (x).end()
int T, n, k;
void solve(){
	cin >> n >> k;
	vector <int> a(n);
	for (int i = 0; i < n; ++ i)
		scanf("%d", &a[i]);
	auto b = a;
	sort(all(b));
	int x = -1, y = n + 1;
	for (int i = 0; i <= n - (n + k + 1) / 2; ++ i){
		if (b[i + (n + k + 1) / 2 - 1] - b[i] < y - x){
			x = b[i];
			y = b[i + (n + k + 1) / 2 - 1];
		}
	}
	cout << x << " " << y << "\n";
	int l = -1, s = 0, p = 0; 
	for (int i = 0; i < n; ++ i){
		s += (x <= a[i] && a[i] <= y ? 1 : -1);
		if (s > p){
			p = s;
			if (s >= 1 && s <= k - 1){
				cout << l + 2 << " " << i + 1 << "\n";
				l = i;
			}
		}
	}
	cout << l + 2 << " " << n << "\n";
}
int main(){
	cin >> T;
	while (T--)
		solve();
	return 0;
}
上一篇:【NOI2022省选挑战赛 Contest4 B】取石子(博弈论)


下一篇:[分层图最短路]飞行路线 洛谷P4568