整體二分是乙個求解區間第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 的子分治過程中處理。...