整體二分是一個常數小的離線做法。
這篇講 $CDQ$ 的文章里提到了其一個分支——整體二分。
整體二分的適用性
有一些問題,在有多組操作(一開始賦初值也算操作)但只有一組詢問的情況下(當然這組詢問正常情況下就放在最后的,不然它后面的操作是擺着玩的),可以二分這個詢問的答案。
二分的時間復雜度是 $O(log(n))$,驗證一個答案是大了還是小了的時間復雜度是 $O(n)$(或者是這個級別的,差不多不到 $O(n^2)$ 就行),總時間復雜度是 $O(n\times log(n))$(或者是這個級別的)。
對於很多組詢問的情況,設詢問組數為 $Q$,如果對每組都二分,時間復雜度是 $O(Q\times n\times log(n))$ 級別的,對於祖傳數據(全 $100000$)直接升天。
然后我們發現,有一些問題的修改對詢問的影響非常有特性,支持高效維護和查詢。這時候就可以把所有操作(包括修改和查詢)放到一塊二分。
總之,整體二分只能在滿足以下條件的情況下使用:
- 單組詢問可以二分
- 存在高效的數據結構維護修改對詢問的影響(了解整體二分的應該知道,像區間修改這種的就不存在)
- 題目可以離線做(廢話)
……
主席樹(模板)
詢問靜態區間第 $k$ 小。$n,Q\le 200000$。
題解
其實也是個整體二分模板題。
如果只有一組詢問,我們可以二分答案,判斷的話就掃一遍詢問區間,看有幾個數比這個答案小,就可以確定這個答案是區間第幾小了。
對於多組詢問的話,我們可以把所有操作扔到一起二分。
具體是什么意思呢?
舉個例子,給出一個數 $x$,在二分答案 $mid$ 時,它只會對所有大於 $mid$ 的答案造成影響。也就是說它跟詢問一樣,可以被二分。
下面說說具體操作:
我們按輸入的順序,依次模擬當前操作區間的操作。(在模擬當前區間的所有操作之前,把之前二分時記錄的信息都清空,即假裝之前啥操作也沒做過)
如果這個操作是賦值操作(在這題就是一開始給數組賦值),
如果賦的數小於等於 $mid$,設這個數為 $x$,把它在樹狀數組/線段樹的第 $x$ 位 $+1$,並把這個操作扔到左遞歸區間(即 $[l,mid]$ 答案區間);
如果賦的數大於 $mid$,直接把這個操作扔到右遞歸區間(即 $(mid,r]$ 區間)。
如果這個操作是詢問操作,
先用樹狀數組/線段樹對其詢問區間求和(這就是之前為什么說在問題比較簡單時 才適合整體二分,你得確保能再用個數據結構維護),也就是查其詢問區間當前有多少個小於等於 $mid$ 的數,這里設有 $x$ 個。
如果 $x$ 小於該查詢的 $k$(即這個查詢問的是區間第 $k$ 小),就把該詢問的 $k$ 減去 $x$(左遞歸區間有足夠的數小於 $mid$,這個詢問的答案顯然在右遞歸區間,於是減去左區間的數的影響),並把這個操作扔到右遞歸區間;
如果 $x$ 大於等於該查詢的 $k$,直接把這個操作扔到左遞歸區間。
如果二分答案的區間 $[l,r]$ 遞歸到了 $l=r$,就把扔到這個遞歸區間的所有詢問操作的答案都賦為 $l$ 或 $r$,回溯。
$Q1:$ 為什么對於賦值操作,賦的數小於等於 $mid$ 時,要把操作扔到左遞歸區間?
$A1:$ 因為我們在詢問操作中,把要遞歸到右子區間的詢問 減掉了小於等於 $mid$ 的數對詢問的影響,也就是說這個賦值操作以后不用管遞歸到右子區間的詢問操作了,於是扔到左子區間即可。
$Q2:$ 那賦的數大於 $mid$ 時,要把操作扔到右遞歸區間?
$A2:$ 因為這個數不會對詢問答案在左子區間的詢問造成影響,它不會參與這些詢問的排名。
其它證明都比較顯然了吧(其實是我沒時間寫)
對於這題,如果不離散化的話,只能用權值線段樹;如果離散化的話,樹狀數組和權值線段樹就都可以用了。
注意一個事情,一開始就要把所有操作的數改為離散化后的數,不要在整體二分里再對每個數套 $map$ 查詢,因為整體二分的時間復雜度頂多做到 $O(n\times log^2(n))$,再套個 $map$ 的 $log$ 可能會爆炸。

1 #include<bits/stdc++.h> 2 #define rep(i,x,y) for(int i=x;i<=y;++i) 3 #define dwn(i,x,y) for(int i=x;i>=y;--i) 4 #define ll long long 5 #define N 400001 6 using namespace std; 7 inline int read(){ 8 int x=0; bool f=1; char c=getchar(); 9 for(;!isdigit(c);c=getchar()) if(c=='-') f=0; 10 for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0'); 11 if(f) return x; 12 return 0-x; 13 } 14 int n,m,a[N],b[N],ans[N],cnt; 15 map<int,int>mp; int lsh[N]; 16 struct query{int x,l,r,k;}q[N],L[N],R[N]; 17 namespace Fwk{ 18 int fwk[N]; 19 inline int lowbit(int x){return x&(-x);} 20 inline void add(int x,int v){ 21 for(;x<=n;x+=lowbit(x)) fwk[x]+=v; 22 } 23 inline int query(int x){ 24 int ret=0; 25 for(;x>0;x-=lowbit(x)) ret+=fwk[x]; 26 return ret; 27 } 28 }; 29 void solve(int ql,int qr,int l,int r){ 30 //printf("solve:%d %d %d %d\n",ql,qr,l,r); 31 if(l==r){ 32 rep(i,ql,qr) if(q[i].x) ans[q[i].x]=l; 33 return; 34 } 35 int mid=(l+r)>>1,cntL=0,cntR=0,tmp; 36 rep(i,ql,qr){ 37 //cout<<"faq:"<<Fwk::query(2)<<' '<<Fwk::query(0)<<endl; 38 if(!q[i].x){ 39 if(q[i].k<=mid) L[++cntL]=q[i], Fwk::add(q[i].l,1); 40 else R[++cntR]=q[i]; 41 //printf("1:%d %d %d\n",q[i].l,q[i].k,mid); 42 } 43 else{ 44 tmp=Fwk::query(q[i].r)-Fwk::query(q[i].l-1); 45 if(tmp<q[i].k) q[i].k-=tmp, R[++cntR]=q[i]; 46 else L[++cntL]=q[i]; 47 //printf("2:%d %d %d %d %d\n",q[i].x,q[i].l,q[i].r,q[i].k,tmp); 48 } 49 } 50 rep(i,1,cntL){ 51 q[ql-1+i]=L[i]; 52 if(!L[i].x) Fwk::add(L[i].l,-1); 53 } 54 rep(i,1,cntR) q[ql-1+cntL+i]=R[i]; 55 solve(ql,ql-1+cntL,l,mid), solve(ql-1+cntL+1,qr,mid+1,r); 56 } 57 int main(){ 58 //freopen("luogu3834.in","r",stdin); 59 //freopen("luogu3834.out","w",stdout); 60 n=read(),m=read(); 61 rep(i,1,n) b[i]=read(), q[i]=(query){0,i,i,b[i]}; 62 sort(b+1,b+n+1); 63 mp[b[1]]=++cnt, lsh[cnt]=b[1]; 64 rep(i,2,n) if(b[i]!=b[i-1]) mp[b[i]]=++cnt, lsh[cnt]=b[i]; 65 int l,r,k; 66 rep(i,1,m) l=read(),r=read(),k=read(), q[n+i]=(query){i,l,r,k}; 67 rep(i,1,n) q[i].k=mp[q[i].k]; 68 solve(1,n+m,1,cnt); 69 rep(i,1,m) printf("%d\n",lsh[ans[i]]); 70 return 0; 71 }
Dynamic Rankings(zoj2112)
詢問動態區間第 $k$ 小。
題解
我們發現,既然可以把所有操作都混到一塊,為什么不能在詢問中間插入一些修改操作呢?
修改操作可以看成減一個數,再加一個數。因此把每個修改操作拆成減去原來的數、加上新的數即可。
注意,對於減去的數,要按它的絕對值進行整體二分的遞歸判斷。因為之前肯定加過這個數,之前那個數對 $mid$ 有影響(給線段樹對應數位加 $1$ 什么的),這個數就對 $mid$ 有影響(比如給線段樹對應數位 $-1$,去掉之前那個數的影響),也就是說這個數實際上就是消去之前加上的那個數的影響,之前那個數在哪,這個數就得在哪。(可以用反證法證明這個性質,想想如果這對加減操作不在同一個操作區間,減法后面的操作會發生什么)
后記
其實大部分整體二分的題目都完全可以用純數據結構(比如主席樹)代替,寫整體二分只是為了減小常數。
了解一下,會寫就好了。