1、概述
樹狀數組(binary indexed tree),是一種設計新穎的數組結構,它能夠高效地獲取數組中連續n個數的和。概括說,樹狀數組通常用於解決以下問題:數組{a}中的元素可能不斷地被修改,怎樣才能快速地獲取連續幾個數的和?
2、樹狀數組基本操作
傳統數組(共n個元素)的元素修改和連續元素求和的複雜度分別爲O(1)和O(n)。樹狀數組通過將線性結構轉換成僞樹狀結構(線性結構只能逐個掃描元素,而樹狀結構可以實現跳躍式掃描),使得修改和求和複雜度均爲O(lgn),大大提高了整體效率。
如圖:
C1=A1
C2=A1+A2
C3=A3
C4=A1+A2+A3+A4
C5=A5
C6=A5+A6
C7=A7
C8=A1+A2+A3+A4+A5+A6+A7+A8
…………
C16=A1+A2+A3+A4+A5+A6+A7+A8+A9+A10+A11+A12+A13+A14+A15+A16
不難發現,C[i] = A[i–2^k+ 1] + … + A[i]其中,k爲i在二進制下末尾0的個數,i從1開始算!
問題到了求2^k了,我們知道,一個正數的相反數的補碼就是這個正數的原碼的反碼再加1,比如:
4的補碼爲0100,-4的補碼即爲1011+1=1100;5的補碼爲0101,-5的補碼即爲1010+1=1011;
6的補碼爲0110,-6的補碼即爲1001+1=1010;7的補碼爲0111,-7的補碼即爲1000+1=1001;
可以看到,每個數與它相反數的補碼相同段剛好就是2^k的補碼!
int Lowbit(int w)
{
return w&(-w);
}
接下來是求前n項和,比如sum(6) = C[6] +C[4];即 sum(i) = C[i] + C[Lowbit(i)] +....
int sum(int pos)//區間求和,向下查找
{
int sum = 0;
while(pos>0)
{
sum += tree[pos];
pos -= Lowbit(pos);
}
return sum;
}
void change(int pos,int num)//單點改值,向上更新,
{
while(pos<=n)
{
tree[pos] += num;
pos += Lowbit(pos);
}
}
傳統的樹狀數組就說這麼多吧,接下來就是線段樹版的樹狀數組了
先看圖,比如求1-4之間的和,先加tree[9]和tree[12]
然後加上tree[5]的和
int tree[2048];//理論上能存放放 [0,2^k) 的樹,其實只能查詢 [1,2^k - 2] 的範圍,但需要開2^(k+1)的空間
void change(int pos,int num)//由底向上
{
pos += M;
for(tree[pos]=num,pos >>= 1;pos;pos >>= 1)
tree[pos] = tree[pos<<1]+tree[pos<<1|1];
}
int presum(int pos)//前綴和
{
int sum = 0;
for(pos += (M+1);pos^1;pos >>= 1)
if(pos&1) //如果是右兒子,就加上左兒子的節點和,相當於依次往前面加
sum += tree[pos-1];
return sum;
}
int getsum(int l,int r)//區間求和
{
int sum = 0;
l--; r++;
for(l += M,r += M;l^r^1;l >>= 1,r >>=1)
{
if((l&1)==0) //這裏要小心, &的優先級沒==高
sum += tree[l+1];//如果l是左兒子的話,就加上右兒子的節點和
if(r&1)
sum += tree[r-1];//如果r是右兒子,就加上左兒子的節點和
}
//直到l和r成爲兄弟節點爲止(這個過程就相當於一個由區間兩邊往中間加的過程)
return sum;
}