1. 程式人生 > >【poj 2104】K-th Number(整體二分+樹狀陣列)

【poj 2104】K-th Number(整體二分+樹狀陣列)

傳送門biu~
題目大意:給一串數字,多次詢問區間的第k小值。
思路:首先考慮一次詢問的情況,我們可以二分答案,然後通過驗證比答案大的數有多少個來不斷地縮小答案範圍直至得到一個準確的答案。而對於多個詢問我們同樣可以這麼做,只不過對每一個詢問我們都需要判定一下,以決定它被劃分到哪一個答案的區間裡。這個判定過程就是通過比較比mid大的數的個數和k。同時如果比二分的mid大的數的個數小於k了,我們是要去尋找小的答案,那麼這些比mid大的數在以後的遞迴裡始終會對答案有貢獻,所以我們沒必要去做重複的工作,只需要把這些數的個數累積到貢獻裡,以後遞迴的時候就不用考慮這些數。我們可以把數列裡的數也和詢問一起遞迴,這樣這些數也會被分到屬於的答案區間裡,並且只對相應區間裡的詢問有影響。
整體二分的過程實質上是個按照數值來劃分操作序列的過程,於是複雜度也就和操作序列的長度線性相關,那麼我們在中間維護一些資訊的時候,就一定不能有和數列長線性相關的東西,否則會破壞其時間複雜度。
具體的複雜度證明請見2013年集訓隊XHR論文。
  

#include <bits/stdc++.h>
#define lowbit(x) (x&(-x))
#define INF 0x3f3f3f3f
#define N 100005
#define M 5005
using namespace std;
int n,m,pos;
int Max=-INF,Min=INF;
int id[N],ans[N],tmp[N];
bool mark[N];
struct DATA{
    int x,v;
    bool operator < (const DATA&r)const{return v<r.v;}
}data[N];
struct Ques{
    int
l,r,k; }q[M]; int tree[N]; inline void add(int x,int num){ while(x<=n){ tree[x]+=num; x+=lowbit(x); } } inline int search(int x){ int re=0; while(x){ re+=tree[x]; x-=lowbit(x); } return re; } void solve(int l,int r,int L,int R){ if(l>r || L==R) return
; int mid=(L+R)>>1; while(data[pos+1].v<=mid && pos<n){ add(data[pos+1].x,1); ++pos; } while(data[pos].v>mid){ add(data[pos].x,-1); --pos; } int cnt=0; for(int i=l;i<=r;++i){ if(search(q[id[i]].r)-search(q[id[i]].l-1)>q[id[i]].k-1){ ans[id[i]]=mid; mark[i]=1; ++cnt; } else mark[i]=0; } int l1=l,l2=l+cnt; for(int i=l;i<=r;++i){ if(mark[i]) tmp[l1++]=id[i]; else tmp[l2++]=id[i]; } for(int i=l;i<=r;++i) id[i]=tmp[i]; solve(l,l1-1,L,mid); solve(l1,l2-1,mid+1,R); } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;++i){ scanf("%d",&data[i].v); data[i].x=i; Max=max(Max,data[i].v); Min=min(Min,data[i].v); } sort(data+1,data+n+1); for(int i=1;i<=m;++i) scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].k); for(int i=1;i<=m;++i) id[i]=i; solve(1,m,Min,Max+1); for(int i=1;i<=m;++i) printf("%d\n",ans[i]); return 0; }