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