整體二分是個很神的東西,它可以把許多復雜的數據結構題化簡。它的精髓在於巧妙地利用了離線的特點,把所有的修改、詢問操作整體把握。
先說說第k大數吧,這種問題是整體二分的標志性題目,什么划分樹啊,主席樹啊,樹套樹啊見了整體二分都得自嘆不如。首先對於一次詢問來說我們可以二分答案,然后通過驗證比答案大的數有多少個來不斷地縮小答案范圍直至得到一個准確的答案。而對於多個詢問我們同樣可以這么做,只不過對每一個詢問我們都需要判定一下,以決定它被划分到哪一個答案的區間里。這個判定過程就是通過比較比二分的mid大的數的個數和k。同時我們看到,如果比二分的mid大的數的個數小於k了,我們是要去尋找小的答案,那么這些比mid大的數在以后的遞歸里始終會對答案有貢獻,所以我們沒必要去做重復的工作,只需要把這些數的個數累積到貢獻里,以后遞歸的時候就不用考慮這些數了。具體地,我們把數列里的數也和詢問一起遞歸,這樣這些數也會被分到屬於的答案區間里,並且只對相應區間里的詢問有影響。如果有修改呢,我們把修改看成一個數就好了,一樣可以隨着遞歸不斷地被划分下去。
我們看到,整體二分的過程實質上是個按照數值來划分操作序列的過程,於是我們的復雜度也就和操作序列的長度線性相關,那么我們在中間維護一些信息的時候,就一定不能有何數列長線性相關的東西,否則會破壞其時間復雜度,具體的復雜度證明請見2013年集訓隊XHR論文。
貼個典型性的代碼吧:
帶修改區間第k小數

1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cmath> 5 #include<cstring> 6 #define maxn 220000 7 #define inf 1000000000 8 9 using namespace std; 10 struct query 11 { 12 int x,y,k,s,tp,cur; 13 }q[maxn],q1[maxn],q2[maxn]; 14 int a[maxn],ans[maxn],tmp[maxn],t[maxn]; 15 int n,m,num,cnt; 16 17 void add(int x,int y) 18 { 19 for (int i=x;i<=n;i+=(i&-i)) t[i]+=y; 20 } 21 22 int ask(int x) 23 { 24 int tmp=0; 25 for (int i=x;i>0;i-=(i&-i)) tmp+=t[i]; 26 return tmp; 27 } 28 29 void divide(int head,int tail,int l,int r) 30 { 31 //cout<<head<<' '<<tail<<' '<<l<<' '<<r<<endl; 32 if (head>tail) return ; 33 if (l==r) 34 { 35 for (int i=head;i<=tail;i++) 36 if (q[i].tp==3) ans[q[i].s]=l;//,cout<<l<<endl; 37 return ; 38 } 39 int mid=(l+r)>>1; 40 for (int i=head;i<=tail;i++) 41 { 42 if (q[i].tp==1&&q[i].y<=mid) add(q[i].x,1); 43 else 44 if (q[i].tp==2&&q[i].y<=mid) add(q[i].x,-1); 45 else 46 if (q[i].tp==3) tmp[i]=ask(q[i].y)-ask(q[i].x-1); 47 } 48 for (int i=head;i<=tail;i++) 49 { 50 if (q[i].tp==1&&q[i].y<=mid) add(q[i].x,-1); 51 else 52 if (q[i].tp==2&&q[i].y<=mid) add(q[i].x,1); 53 } 54 int l1=0,l2=0; 55 for (int i=head;i<=tail;i++) 56 if (q[i].tp==3) 57 { 58 if (q[i].cur+tmp[i]>q[i].k-1)//q[i].cur+tmp[i]表示累積了多少個數 59 q1[++l1]=q[i]; 60 else 61 { 62 q[i].cur+=tmp[i]; 63 q2[++l2]=q[i]; 64 } 65 } 66 else 67 { 68 if (q[i].y<=mid) q1[++l1]=q[i]; 69 else q2[++l2]=q[i]; 70 } 71 for (int i=1;i<=l1;i++) q[head+i-1]=q1[i]; 72 for (int i=1;i<=l2;i++) q[head+l1+i-1]=q2[i]; 73 divide(head,head+l1-1,l,mid); 74 divide(head+l1,tail,mid+1,r); 75 } 76 77 int main() 78 { 79 //freopen("ranking.in","r",stdin); 80 //freopen("ranking.out","w",stdout); 81 scanf("%d%d",&n,&m); 82 for (int i=1;i<=n;i++) 83 { 84 scanf("%d",&a[i]); 85 q[++num].x=i; q[num].y=a[i]; 86 q[num].tp=1; q[num].s=0; 87 } 88 char sign; 89 int x,y,z; 90 for (int i=1;i<=m;i++) 91 { 92 scanf("\n%c",&sign); 93 if (sign=='Q') 94 { 95 scanf("%d%d%d",&x,&y,&z); 96 q[++num].x=x,q[num].y=y,q[num].k=z; 97 q[num].tp=3; q[num].s=++cnt; 98 } 99 else 100 { 101 scanf("%d%d",&x,&y); 102 q[++num].x=x; q[num].y=a[x]; 103 q[num].tp=2; q[num].s=0; 104 q[++num].x=x; q[num].y=y; 105 q[num].tp=1; q[num].s=0; 106 a[x]=y; 107 } 108 } 109 divide(1,num,0,inf); 110 for (int i=1;i<=cnt;i++) 111 printf("%d\n",ans[i]); 112 return 0; 113 }
樹狀數組什么的我最喜歡了,又短又快!
代碼里有許多可以縮起來寫的地方,我沒改,目測這樣也比主席樹短不少......