上一節是線段樹,基於區間二分的方法,雖然空間需求是O(n),但最大可能要4*n的數組,比較費空間,寫起來也比較麻煩。如果需求比較單純,完全可以使用更輕巧的資料結構。樹狀數組英文是Binary Indexed Tree,這更反映出它的實質。
從同學BYVoid的blog裡抄點東西,NOI金牌得主作品,強烈推薦 www.byvoid.com/blog
樹狀數組是一個優美小巧的資料結構,在很多時候可以代替線段樹。一句話概括就是,凡是樹狀數組可以解決的問題,線段樹都可以解決,反過來線段樹可以解決的問題,樹狀數組不一定能解決。
樹狀數組英文名稱為Binary Index Tree,直譯過來就是二進位索引樹,我覺得二進位索引樹更能說明其本質。樹狀數組的本質就是一種通過二進位位來維護一個序列前i和的資料結構。
對於維護的序列A,定義C[i]=A[j+1]+…+A[i],其中j為i的二進位表示中把最右邊的1換成0的值。j的值可以通過lowbit求出,即i-lowbit(i)。
lowbit(a)為2^(a的二進位表示末尾0的個數)。可以用下面式子求出
a&((~a)+1)
或者根據補碼的性質簡化為 a&-a
修改方式:
void modify(int p,int delta) { while (p<=N) { C[p]+=delta; p+=lowbit(p); } }
求首碼和:
int sum(int p) { int rs=0; while (p) { rs+=C[p]; p-=lowbit(p); } return rs; }
引述完畢。補張圖直觀地展現一下樹狀數組的樣子。求和方式代碼中已給出。
樹狀數組的最大優勢在於向高維擴充的便捷性。拿兩維樹狀數組舉例。在一維情況當中,c[i] = a[i-lowbit(i)+1]+...+a[i],到兩維,自然想到 c[i,j] 應該是從c[i-lowbit(i+1)+1][j-lowbit(j)+1]的矩形地區。繼續盜圖,這次是胡偉棟學長,它表明了15*15的二維樹狀數組的分割情況:
拿一個例子說明具體操作。POJ1195
題意為一個S*S的二維數組,可以對其中的任意元素增減,求任意矩形塊的和。首先,從原點開始的矩形塊可以通過一個二重迴圈求。對任意矩形li,lj,hi,hj,可以轉化成4個首碼和,。
上代碼
#include <iostream>#include <cstdio>#include <cstring>#include <queue>#include <cmath>using namespace std;typedef long long int64;#define lowbit(x) ((x)&(-x))const int BITREE_SIZE_MAX=1025;int bitree_size;int bitree_2d[BITREE_SIZE_MAX+1][BITREE_SIZE_MAX+1];void bitree_init_2d(int size){bitree_size=size;for(int i=1;i<=size;++i){memset(bitree_2d[i]+1,0,size*sizeof(int));}}void bitree_change_2d(int i,int j,int dif){int j_init=j;while(i<=bitree_size){j=j_init;while(j<=bitree_size){bitree_2d[i][j]+=dif;j += lowbit(j);}i += lowbit(i);}}int64 bitree_sum_2d(int i,int j){if(i==0 || j==0){return 0;}int64 r=0;int j_init=j;while(i>0){j=j_init;while(j>0){r+=bitree_2d[i][j];j -= lowbit(j);}i -= lowbit(i);}return r;}int64 bitree_sum_2d(int li,int lj,int hi,int hj){return bitree_sum_2d(hi,hj) - bitree_sum_2d(li-1,hj)-bitree_sum_2d(hi,lj-1)+bitree_sum_2d(li-1,lj-1);}int main(){//freopen("in.txt","r",stdin);int op;int pa,pb,pc,pd;cin>>op>>pa;bitree_init_2d(pa);while(true){/*for(int i=1;i<=bitree_size;++i){for(int j=1;j<=bitree_size;++j){cout<<bitree_sum_2d(i,j)<<' ';}cout<<endl;}*/scanf("%d",&op);switch(op){case 1:scanf("%d%d%d",&pa,&pb,&pc);bitree_change_2d(pa+1,pb+1,pc);break;case 2:scanf("%d%d%d%d",&pa,&pb,&pc,&pd);cout<<bitree_sum_2d(pa+1,pb+1,pc+1,pd+1)<<endl;break;default:return 0;}}return 0;}
下一節是RMQ問題特例,使用單調隊列。
------------------------------------------------------------------------------------------------------------------
My Blog: http://www.npbool.info