传送门
文章目录
目录:
题意:
给你一个长度为 n n n的数组 a a a以及 k k k,让你选择一个值域 [ x , y ] [x,y] [x,y],满足能将该数组分成连续的 k k k段并且每段中值域在 [ x , y ] [x,y] [x,y]内的个数严格大于不在其范围内的数的个数,要求你给出 [ x , y ] [x,y] [x,y]并且让 y − x y-x y−x最小,让后给出分段的方案。
1 ≤ k ≤ n ≤ 2 e 5 , 1 ≤ a i ≤ n 1\le k\le n\le 2e5,1\le a_i\le n 1≤k≤n≤2e5,1≤ai≤n
思路:
一开始想错了,二分了 x x x和 y y y还感觉很对。。。
可以发现 [ x , y ] [x,y] [x,y]这是一个连续的区间,我们可以利用这个进行尺取,现在问题就变成了如何检验给定 [ x , y ] [x,y] [x,y]是否合法。
我们将值域在 [ x , y ] [x,y] [x,y]内的数看成 1 1 1,不在值域内的数看成 − 1 -1 −1,设新数组 p r e pre pre就是将 a a a换成 1 1 1和 − 1 -1 −1后的前缀和数组,那么一个子区间合法的条件就是 [ l , r ] [l,r] [l,r]满足 p r e [ r ] − p r e [ l − 1 ] > 0 pre[r]-pre[l-1]>0 pre[r]−pre[l−1]>0,顺着这个思路,我们不难发现分段方案就是在 p r e pre pre数组中找一个从0开始的上升序列,长度为 k + 1 k+1 k+1,那么最终 p r e [ n ] pre[n] pre[n]一定需要满足 p r e [ n ] > = k pre[n]>=k pre[n]>=k,此时就比较好搞了,我们先尺取求出来 [ x , y ] [x,y] [x,y],判断条件就是 p r e [ n ] > = k pre[n]>=k pre[n]>=k, p r e [ n ] pre[n] pre[n]是所有数的和,只需要存一下当前数有几个即可,最后输出方案。
复杂度 O ( n ) O(n) O(n)
#include<bits/stdc++.h>
#define X first
#define Y second
#define L (u<<1)
#define R (u<<1|1)
#define Mid (tr[u].l+tr[u].r>>1)
#define pb push_back
using namespace std;
const int N=1000010,INF=0x3f3f3f3f,mod=1e9+7;
typedef long long LL;
int n,k;
int a[N],cnt[N];
void solve() {
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),cnt[a[i]]++;
int x,y,sum=-n;
x=0; y=INF;
for(int l=1,r=0;r<=n;) {
while(r+1<=n&&sum<k) sum+=cnt[++r]*2;
if(sum<k) break;
if(r-l+1<y-x+1) x=l,y=r;
sum-=cnt[l++]*2;
}
printf("%d %d\n",x,y);
sum=0;
int cntt=1;
int l=1;
for(int i=1;i<=n;i++) {
if(a[i]>=x&&a[i]<=y) sum++;
else sum--;
if(sum==cntt) {
cntt++;
if(cntt==k+1) {
printf("%d %d\n",l,n);
break;
}
else printf("%d %d\n",l,i),l=i+1;
}
}
for(int i=1;i<=n;i++) cnt[a[i]]=0;
}
int main() {
int _; scanf("%d",&_);
while(_--) {
solve();
}
}