1. 程式人生 > >6231 K-th Number 二分 + 尺取法(好題)

6231 K-th Number 二分 + 尺取法(好題)

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;
}