Range and Partition (构造+尺取)
题目大意:
给一个长度为 \(n\) 的数组 \(a\), 找到一个区间 [x,y], 使得 a 可以分成 k 个子数组,满足 3 个条件:
- 子数组是 a 中连续的元素组成的
- a 中每个元素分到某个子数组里
- 在每个子数组中,位于[x,y] 区间内的元素个数要严格大于剩余元素个数。
最小化 \(y - x\)
数据范围
n 不超过 \(2 \cdot 10^5\) 1<=a[i]<=n
输入输出
输入: 样例个数t n k 数组a
输出: x,y 每个子数组的两端
解题思路
- 考虑区间 [x,y] 固定时问题的解法。假设有解,那么考虑构造,只需从头到尾,每次遍历到满足要求的数比剩余数多1时,划分出这一个数组。划分出(k-1)个数组时,最后一个单独留出来即可。
- 那么 区间[x,y]在什么时候有解? 既然要求 每个子数组在[x,y]内的数目严格大,也就是每个子数组内这样的数比其他数至少多1。划分为k个子数组,则这样的数比其他数至少多k。所以设整个数组有\(w\)个数在区间内,若满足 \(w-(n-w) \ge k\) 即 \(w \ge \frac{n+k}{2}\) 即可。
- 因此,可以先遍历一遍数组 a ,得到每个数的数目的数组 b ,然后用尺取法遍历 b ,得到差最小的 x , y。最后再用x,y划分出最终结果。
复杂度 \(O(n)\)
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
int n,k;
int a[200005];
int b[200005];
int x,y;
signed main()
{
int t;cin >> t;
while(t--)
{
cin >> n >> k;
for(int i=1;i<=n;++i)
{
cin >> a[i];
b[i] = 0;
}
for(int i=1;i<=n;++i)
b[a[i]]++;
int le=1,ri=1;
int sum=0,p=ceil((double)(n+k)/2.0);
int minlen = 1234456778;
while(ri<=n)
{
while(ri<=n&&sum<p)
sum += b[ri++];
if(sum>=p&&ri-le<minlen) {minlen=ri-le;x=le;y=ri-1;}
while(le<ri&&sum>=p)
{
sum -= b[le++];
if(sum>=p&&ri-le<=minlen) {minlen=ri-le;x=le;y=ri-1;}
}
}
cout << x << " " << y << endl;
int ins=0,outs=0;
int num = 0;
le = 1;
for(int i=1;i<=n;++i)
{
if(num==k-1) break;
if(a[i]<=y&&a[i]>=x) ins++;
else outs++;
if(ins>outs){
cout << le << " " << i << endl;
le = i + 1;
ins = outs = 0;
num++;
}
}
cout << le << " " << n << endl;
}
return 0;
}