整體二分 區間第 K 小(大)問題 詳解

2021-09-25 07:39:28 字數 4823 閱讀 6824

整體二分是乙個求解區間第k小(大)非常優秀的演算法,但是要求離線處理,對於所有詢問做整體的二分答案操作。相較於主席樹 ,樹套樹,整體二分( 應該 )更加優秀。

我用主席樹與整體二分寫,並沒有發現在時間上整體二分快多少,我自己算時間複雜度也覺得兩者差不多(也可能我寫的太醜了),但是空間上整體二分當然非常佔優

上問題:(對於整體二分帶修改的其實和不帶修改其實差不多,後面會說)

給定乙個長度為n數列,進行m次詢問,每次詢問 l 到 r 的 第 k 小的數是多少。

首先,我們考慮怎樣對乙個詢問進行二分答案求解,從-inf到inf不斷二分答案,每一次二分後,在數列中求出所詢問區間內小於等於mid的個數num,如果num>=k,說明在這num個小於等於mid的數當中包含所求答案,反之答案則在區間大於mid的數當中,這時,更新數列為原數列中小於等於mid數,或者其他大於mid的數,便於下次二分答案。

而求解區間內有多少個小於等於mid的數時,單次詢問當然直接遍歷,但是在整體二分時,面對許多詢問,我們可以用樹狀陣列,把數列中小於等於mid的數的位置下標標記,然後sum(r) - sum(l-1)求出此區間小於等於mid的數。為了方便大家理解,***對於單組詢問也做樹狀陣列的處理

整體二分即是把這種二分答案操作做出整體把握,對所有詢問二分,所以一定要理解這種求解方式

下面我給出模擬過程:

首先乙個長度為7數列: 6(1) 2(2) 5(3) 3(4) 1(5) 9(6) 2(7) 詢問 2 到 6 的第 3 小數,括號裡是位置下標

an:a1 a2 …an 數列

l :二分答案的左區間

r :二分答案的右區間

l:詢問區間的左區間

r:詢問區間的右區間

k: 第幾小

因為數列中最小為1,最大為9,我令 l = 1 , r = 9;

首先初始狀態:

an: 6(1) 2(2) 5(3) 3(4) 1(5) 9(6) 2(7)

l = 1 , r = 9 , l = 2 , r = 6 , k = 3

1:

mid = (l+r)>>1 = 5;

插入 2(2) 5(3) 3(4) 1(5) 2(7) 的位置到樹狀陣列。

查詢 num = sum® - sum(l-1) = 4;

發現 num >= k , 說明答案小於等於5;

所以 令 r = mid;

並更新數列為: 2(2) 5(3) 3(4) 1(5) 2(7)

2:

mid = (l+r)>>1 = 3;

插入 2(2) 3(4) 1(5) 2(7) 的位置到樹狀陣列。

查詢 num = sum® - sum(l-1) = 3 ;

發現 num >= k , 說明答案小於等於3;

所以 令 r = mid;

並更新數列為: 2(2) 3(4) 1(5) 2(7)

3:

mid = (l+r)>>1 = 2;

插入 2(2) 1(5) 2(7) 的位置到樹狀陣列。

查詢 num = sum® - sum(l-1) = 2 ;

發現 num < k , 說明答案大於3;

所以 令 l = mid+1;k = k - 2;

並更新數列為: 3(4)

4:

l = r = 3 , 所以答案為3;

整體二分則是在上述的基礎上,把詢問打包存下來,每次先遍歷數列,插入小於等於mid的數的位置,再處理所有詢問,如果該詢問的答案小於等於mid,就對應到小於等於的mid的新數列裡,反之對應另一邊,這樣不斷整體分劃,直到l==r時,此時對應的某些詢問,答案就都是l。

還是上**,理解了上面,**多看幾遍應該就ok了

區間第k小

#include

using namespace std;

const

int maxn=

1e5+

5, maxm=

1e4, inf=

1e9+7;

int n,m,cnt;

int ans[maxn]

;struct node

;node z[maxn+maxm]

,le[maxn+maxm]

,ri[maxn+maxm]

;int sum[maxn]

;int

lowbit

(int x)

void

add(

int x,

int d)

}int

query

(int x)

return ans;

}void

cdq(

int l,

int r,

int l,

int r)

int mid =

(l+r)

>>1;

int cntl =

0, cntr =0;

for(

int i=l;i<=r;i++

)else

}for

(int i=

1;i<=cntl;i++)if

(!le[i]

.tp)

add(le[i]

.pos,-1

);// 取消標記,使樹狀陣列清空,方便下次操作

for(

int i=

1;i<=cntl;i++

) z[l+i-1]

= le[i]

;// 以mid為界限分劃為兩個新整體,數列與詢問都分開了

for(

int i=

1;i<=cntr;i++

) z[l+cntl+i-1]

= ri[i]

;cdq

(l,mid,l,l+cntl-1)

;cdq

(mid+

1,r,l+cntl,r);}

intmain()

;// z[i].tp==0 表示插入操作

}for

(int i=

1;i<=m;i++);

// z[i].tp==1 表示詢問

}cdq

(-inf,inf,

1,cnt)

;for

(int i=

1;i<=m;i++

) cout<<}

修改其實就只是比不修改的多了乙個刪除,樹狀陣列中取消標記就好了

我直接上個需要修改的**,與上面的大致一樣

區間第k小帶修改

#include

using namespace std;

const

int maxn=

5e4+

5, maxm=

1e4+

5, inf=

1e9+7;

int n,m,va[maxn]

,ans[maxm]

;struct node

;node z[maxn*2]

,zl[maxn*2]

,zr[maxn*2]

;int c[maxn]

;void

add(

int x,

int d)

}int

sum(

int x)

return s;

}void

cdq(

int l,

int r,

int l,

int r)

int mid =

(l+r)

>>1;

int p =

0, q =0;

for(

int i=l;i<=r;i++

)else

if(z[i]

.tp==2)

else

}for

(int i=

1;i<=p;i++

)for

(int i=

1;i<=p;i++

) z[l+i-1]

= zl[i]

;for

(int i=

1;i<=q;i++

) z[l+p+i-1]

= zr[i]

;cdq

(l,mid,l,l+p-1)

;cdq

(mid+

1,r,l+p,r);}

intmain()

;// z[i].tp==1 表示插入

}char d;

for(

int i=

1;i<=m;i++);

// z[i].tp==3 表示詢問

}else

;// z[i].tp==2 表示刪除

z[++ki]

=(node)

;// 先刪除原位置,再新增新位置

va[a]

= b;}}

cdq(

0,inf,

1,ki)

;for

(int i=

1;i<=cnt;i++

) cout<<}}

不想拿金的acmer不是好acmer

HDU 4417 (二分 區間第k大)

查詢區間 l,r 中有多少數 比給定的 h 小。我們可以這麼想,h 一定會比 區間的第 x 大 第 x 1 小。怎麼確定 x。這是乙個單調的問題,所以二分 區間第 k 大就可以確定了。include include include include using namespace std const ...

靜態區間第K小(整體二分 主席樹)

題目鏈結 題解主席樹入門題 但是這裡給出整體二分解法 整體二分顧名思義是把所有操作放在一起二分 想想,如果求 1 n 的第 k 小怎麼二分求得?我們可以二分答案 k o n 統計有多少個數小於等於 k 如果對於每個詢問都這麼搞,肯定不行 我們可以發現,如果每次都搞一次,有許多算重複的地方 div l...

複習 整體二分求區間第K大

給定乙個長度為n的序列與m個 l,r,k 詢問區間 l,r 中第k大。考慮單個詢問的解決,我們可以二分答案然後統計比他大的數的個數。那麼時間複雜度就是n log m.但多個詢問如何解決?我們可以整體地二分答案區間 l,r 然後將詢問集合分割,分到 l,mid 與 mid 1,r 的子分治過程中處理。...