1.题意:给你n个数字的序列,让你把任意一个连续区间内第k大的数字插入到数组B里面,最后求B中第m大的数字。
2.分析:
(1)比赛到时候致命的一个错误就是:把第k大的数字理解反了。。2 3 1第三大 , 自以为是3了(应该是1)。。然后按照错的题意 意*了各种做法。。而且开场第一题我给读漏了条件,唉。。读题太差了。。昨天那场直接自闭了。
注:对于题意还有一坑:所有第k大的数字都要插入B,不论是否重复,比如2 2 3 3 3第二大的数字就是 3 而不是2 。。
(2)理清题意以后,二分的思路还是很难想的,这种题二分了怎么验证也是这个题的难点,你得想到二分还得想到怎么验证。(好了不扯犊子了)
(3)对于二分思想:
假设我们设定x为B中第m大的数字,也就是说B中比x大的还有(m - 1)个,这(m-1)个数字作为某个区间第k大的数字被插入了(m-1)次:
<1>若大于 x 的数字y(y>x) 作为区间第k大的数字 出现了 n(n> m - 1)次,也就是往B里面插入了>=m次 , 这时B中第m大的数字肯定 > x , 所以这时候真实答案应该大于x。
<2>若若大于 x 的数字y(y>x) 作为区间第k大的数字 出现了 n(n<= m - 1)次,也就是往B里面插入了<=m-1次,这时B中第m大的数字肯定 <= x , 所以可能存在比x还小的数字,我们应该再取 比x小的数字验证。
(4)验证思想(尺取法/滑动窗口):怎么寻找大于等于X的数字作为区间第k大的数字的区间个数有多少个呢?
我们动态维护一个含有比x大的数字有k个的区间,若此区间左右边界为[ l , r ],则这样的区间还有(n - r)个,左侧区间已经满足了临界,右侧区间情况都要加上。然后左端点不断右移,维护这样的区间,求其个数判断与m-1的关系即可
3.隐藏坑点:m要开long long ,题目没指明范围。。
4.代码:
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 100000 + 100;
LL num[maxn],order[maxn];
int n,k;
LL m;
bool judge(LL p){//尺取验证
LL sum = 0;//记录区间和
int cnt = 0;//记录在这个区间内大于p的数字个数
int r = -1;//右端点
int l = 0;//左端点
while(r<n){
if(cnt<k){//先找齐大于p的k个数字在这个区间内为临界
if(r+1<n&&num[r+1]>p)cnt++;//用r+1的原因是保持是在[ l, r]内有k个大于p的数字,若用r,
r++;//则有k个大于p的数字所在区间为[ l, r-1 ]
}
else{//求这个区间所有数目
if(cnt==k)sum+=(n - r);//临界区间之后的
if(sum>m-1)return false;
if(num[l]>p)cnt--;//右移左端点
l++;
}
}
return true;
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d%lld",&n,&k,&m);
for(int i = 0;i<n;i++){
scanf("%lld",&num[i]);
order[i] = num[i];
}
sort(order,order + n);
int l = 0,r = n-1;
LL ans = order[0];
while(l<=r){//二分答案
int mid = (l+r)>>1;
if(judge(order[mid])){//验证<=m-1,则可能有更小的
//cout<<order[mid]<<endl;
ans = order[mid];
r = mid-1;
}
else l = mid+1;//否则只能比当前大
}
printf("%lld\n",ans);
}
return 0;
}