Newcoder 14301 K-th Number(二分)

题目描述

Alice are given an array A[1…N] with N numbers.
Now Alice want to build an array B by a parameter K as following rules:
Initially, the array B is empty. Consider each interval in array A. If the length of this interval is less than K, then ignore this interval. Otherwise, find the K-th largest number in this interval and add this number into array B.
In fact Alice doesn’t care each element in the array B. She only wants to know the M-th largest element in the array B. Please help her to fi nd this number.

输入描述:

 The first line is the number of test cases. For each test case, the first line contains three positive numbers N(1≤N≤105);K(1≤K≤N);M.
 The second line contains N numbers Ai(1≤Ai≤109).It's guaranteed that M is not greater than the length of the array B.

输出描述:

For each test case, output a single line containing the M-th largest element in the array B.

示例1

输入

2
5 3 2
2 3 1 5 4
3 3 1
5 8 2

输出

3
2

题目翻译:

给一个含有 n n n个数字的数组 A A A,把所有长度大于等于 k k k的区间中第 k k k大的数插入到 b b b数组中,问 b b b数组的第 m m m大数是多少

首先根据数据规模 1 < = N < = 1 e 5 1<=N<=1e5 1<=N<=1e5果断放弃暴力解法

看了大佬的题解后发现此题可以用二分!我们二分出一个最终答案mid,检验这个答案的可行性,也就是说,看这个mid在原来的 a a a数组中到底能不能找到大于等于 M M M个区间,使得这些区间的第 k k k大数放到 b b b数组中,能够让mid在 b b b数组中是第M大数,具体方法如下:

①开两个变量,num记录当前区间大于等于mid数字的个数,cnt记录区间个数。开两个指针 i , j i,j i,j,初始二者都等于数组最左端(我这里为1)

	int num=0,cnt=0;
    for(int i=1,j=1;j<=n;j++){
        
        
    }

②开始枚举, j j j指针逐渐右移,枚举的过程中判断 a [ j ] a[j] a[j]是否大于等于 m i d mid mid,如果是, n u m + + num++ num++,一旦 n u m = = k num==k num==k,暂时停止,说明目前的区间 [ i , j ] [i,j] [i,j]中,已经出现了 k k k个值大于等于 m i d mid mid了。此时思考一下,对于 j j j后面的数字而言,如果它比 m i d mid mid小,那么必然不会影响到区间有第 k k k大的值大于等于 m i d mid mid;如果它比 m i d mid mid大呢,同理一样不会影响。所以此时 [ i , j ] , [ i , j + 1 ] , [ i , j + 2 ] , . . . , [ j , j + n ] [i,j],[i,j+1],[i,j+2],...,[j,j+n] [i,j],[i,j+1],[i,j+2],...,[j,j+n]都是满足题意的,所以 c n t + = n j + 1 cnt+=n-j+1 cnt+=nj+1

for(int i=1,j=1;j<=n;j++){
        if(a[j]>=mid) num++;
        if(num==k){
            cnt+=n-j+1;
            while(a[i]<mid){
                cnt+=n-j+1;
                i++;
            }
            num--;i++;
        }
    }

③此时还没完,你想想 i i i指针是不是还可以右移,如果 a [ i + C ] a[i+C] a[i+C](C为常数)比 m i d mid mid小,那么这个数是不是存在的毫无意义,所以可以将 a [ i + C ] a[i+C] a[i+C]干掉,得到新区间 [ i + C + 1 , j ] [i+C+1,j] [i+C+1,j]。同理对于 j j j右边的数字而言,存在着和②中叙述完全相同的事实,所以我们又新增了区间 [ i + C + 1 , j + 1 ] , [ i + C + 1 , j + 2 ] , . . . [ i + C + 1 , j + n ] [i+C+1,j+1],[i+C+1,j+2],...[i+C+1,j+n] [i+C+1,j+1],[i+C+1,j+2],...[i+C+1,j+n] c n t + = n j + 1 cnt+=n-j+1 cnt+=nj+1

④最后检查 c n t > = m cnt>=m cnt>=m,如果是,return 1,否则 return 0;

return cnt>=m;

完整的代码如下:

#include<bits/stdc++.h>
#define int long long
typedef long long ll;
#define js ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using namespace std;
int n,k,m;
int a[100005];
bool check(int mid){
    int num=0,cnt=0;
    for(int i=1,j=1;j<=n;j++){
        if(a[j]>=mid) num++;
        if(num==k){
            cnt+=n-j+1;
            while(a[i]<mid){
                cnt+=n-j+1;
                i++;
            }
            num--;i++;
        }
    }
    return cnt>=m;
}
signed main(){
    
    int t;
    cin>>t;
    while(t--){
        cin>>n>>k>>m;
        for(int i=1;i<=n;i++){
            cin>>a[i];
        }
        int l=1,r=1e9;
        while(l<=r){
            int mid=(l+r)>>1;
            if(check(mid)) l=mid+1;
            else r=mid-1;
        }
        cout<<l-1<<endl;
    }
    return 0;
}

多说两句:

我的解题步骤会尽可能详细,尽可能不跳步,目的就是让自己以及大家明白中间的思维过程~

这个题存在一定的难度,但是在真正比赛中应该属于签到级别吧,继续努力吧~!