题目:点击打开链接
题意:
给一个长度为n的数组a,取一个子区间的第k大,区间长度小于k的区间忽略。把所有子区间的第k大放入b数组(可以重复放,所以b数组要比a数组大很多,所以不是要真的去模拟),求这个b数组的第m大。
思路:
显然答案是数组a中的一个数。
所以可以把a全放到b中然后排序,二分查找答案。
这个答案可以判断一下(尺取法):
答案设为x,cnt表示区间 [ l, r ]内大于等于x的个数。
当cnt<k时,
尺子右端向右移,r++;
当cnt>=k时,
从区间[l, r] 到 区间 [l, n - 1]的所有区间第 k 大都是大于等于 x 的
例 2 3 1 5 4 1 1和2 3 1 5 4 6 6 (l=0,r=5,其他同样例)
把这些区间加起来,sum+=(n-r)
如果sum>=m,可以保证这个x起码是第m大,但有可能是第m+1大,m+2大,
换句话说,符合样例一的区间有,2,3,1,5,4和3,1,5,4,第3大都是3,所以m=2时,答案为3,所以遍历到的x起码要有两个区间,
但有两个以上区间的x不一定是就是答案,
所以要再往右找到最贴近第m大的。
因为x=2时是包含x=3的,所以往右贴近。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+30;
int a[maxn],b[maxn];
ll n,m,k;
ll check(int x){
int l=0,r=-1,cnt=0;
ll sum=0;
while(r<n){
if(cnt<k){
r++;
if(a[r]>=x)
cnt++;
}
else{
sum+=(n-r);
if(a[l]>=x)
cnt--;
l++;
}
}
return sum;
}
int main(){
int T;
cin>>T;
while(T--){
cin>>n>>k>>m;
for(int i=0;i<n;i++){
cin>>a[i];
b[i]=a[i];
}
sort(b,b+n);
int l=0,r=n-1;
ll ans;
while(l<=r){
int mid=(l+r)>>1;
int t=b[mid];
if(check(t)>=m){
ans=t; //最大的那个才恰好是答案
l=mid+1;
}
else
r=mid-1;;
}
cout<<ans<<endl;
}
return 0;
}