比赛链接:
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;
}