一.概念程序員
樹狀數組(Binary Indexed Tree(B.I.T), Fenwick Tree)是一個查詢和修改複雜度都爲log(n)的數據結構。主要用於查詢任意兩位之間的全部元素之和,可是每次只能修改一個元素的值;通過簡單修改能夠在log(n)的複雜度下進行範圍修改,可是這時只能查詢其中一個元素的值(若是加入多個輔助數組則能夠實現區間修改與區間查詢)。算法
要想查看本題,請點這裏(有一些區別,本質相同)數組
若是直接用數組進行模擬,修改的時間複雜度是O(1),查詢是O(n),m次查詢操做的時間複雜度就是O(mn),時間複雜度太高。數據結構
樹狀數組就能夠輕鬆處理這類問題,它是一個查詢和修改的複雜度都爲log(n)的數據結構。函數
A表明原來的數組,C表明樹狀數組。爲何樹狀數組要長成這樣?優化
明確一點:樹狀數組是對二進制的應用。spa
咱們不妨把全部的數字都轉換爲二進制。來觀察一下數字特徵:code
舉幾個例子:blog
用C來表明樹狀數組,用A來表明原數組:圖片
C(2) = A(1)+ A(2) 對應二進制 C(0010) = A(0010) + A(0001)
C(4) = A(4)+A(2)+A(3) 對應二進制C(0100) = A(0100)+A(0010)+ A(0011)
C(8) = A(8)+A(4)+A(6)+A(7)對應二進制C(1000) = A(1000)+A(0100)+A(0110)+A(0111)
......
不難發現,對於任意的C[i]寫成二進制的形式,都等於原來的數組A[i]的值自己,加上把i轉換成二進制後,把首位變成0,而且開始逐位向後把0變成1,相加。
例如: 1000(二進制)向後逐位變化,即1000--> 0100-->0110>0111
......
構建出這樣的樹狀數組後:
查詢A1到A8的和,只須要返回C8
查詢A1到A7,只需返回C7+C6+C4
查詢A1到A6,只須要返回C6+C4
以此類推。
這樣便優化了詢問的時間複雜度。
那麼說的這麼好聽,如何作到修改A的值時,順便更新C的全部關於A的值呢?(例如,修改A[1]的時候更新C1,C2,C4,C8)
三.補碼、lowbit函數
1.補碼
首先說一說補碼。
——百度百科。
補碼是計算機表示符號數的一種方式,概念內容太雜亂,對於咱們樹狀數組沒有太大的用處,只須要了解兩個事情:
·正整數的補碼和原碼(原來的二進制的代碼)相同。
·負整數的補碼將其正整數的原碼全部爲取反(0變1,1變0),以後加一。
2.lowbit函數
lowbit函數即是幫助咱們找到二進制表達式中最低位1所對應的值。
好比,6的二進制是110,因此lowbit(6)=2。
lowbit的實現方式一共兩種:我的推薦下面這種寫法:
int lowbit(int x) { return x&(-x); }
&符號意思爲按位與,把兩個數的二進制一位一位比較,都爲1,結果則爲1,不然就是0。
這個函數什麼意思?將一個數x的原碼和補碼按位與?返回的就是最後一位1?
的確是這樣。
舉個例子(example):
6 = 0110(二進制)
它的補碼爲0010。按位與以後獲得的確實就是最後一位1。
正是由於補碼在取反後+1,纔有了lowbit函數的產生。
您不妨打開計算器,切換到程序員模式,試上幾組,或許會有新的領悟。
畢竟「紙上得來終覺淺,絕知此事要躬行」。
......
3.lowbit查詢與更新的應用:
首先先來運行一段代碼:
#include<stdio.h> int main() { int i,j; for(i=1;i<=8;i++) { printf("%d:",i); for(j=i;j<=8;j+=j&-j) printf("%d ",j); printf("\n"); } return 0; }
Output:
1: 1 2 4 8
2: 2 4 8
3: 3 4 8
4: 4 8
5: 5 6 8
6: 6 8
7: 7 8
8: 8
圖片數字對比來看更新A1,須要更新C1,C2,C4,C8。
更新A2,須要更新C2,C4,C8。更新A3,須要更新C3,C4,C8.....
正好與咱們代碼運行出來的結果一致。這就爲咱們向上更新提供了條件。
再來看一下查詢。若是查詢A1到A8和,只須要返回C8,查詢A1到A7,只須要返回C7+C6+C4
再來看下面這組代碼:
#include<stdio.h> int main() { int i,j; for(i=1;i<=8;i++) { printf("%d:",i); for(j=i;j;j-=j&-j) printf("%d ",j); printf("\n"); } return 0; }
Output:
1: 1
2: 2
3: 3 2
4: 4
5: 5 4
6: 6 4
7: 7 6 4
8: 8
這段代碼只是將以前的i+=lowbit(i)修改成了i-=lowbit(i)
再對比以前原圖,查詢A1到A8,只須要返回C8,查詢A1到A7,只須要返回C7,C6,C4與咱們代碼運行的效果一致,這就爲咱們向下查詢提供了條件。
四.樹狀數組應用
再回頭看以前引出樹狀數組的題目,這時候就能夠有必定的思路了。
樹狀數組主要的函數分爲兩個,即更新函數和查詢函數。
void fix(int x) { int i; for(i=x;i<=n;i+=i&-i) //向上更新
e[i]++; //一維樹狀數組e
} //注意fix()的形參值x<=0時死循環。
int getsum(int x) { int ret=0,i; //返回值爲ret,初值爲0
for(i=x;i;i-=i&-i)//向下查詢
ret+=e[i]; return ret; } //注意getsum()的形參值x<0時e[ ]數組越界。
void fix(int x) { int i; for(i=x;i;i-=i&-i) //向下更新
e[i]++; } int getsum(int x) { int ret=0,i; for(i=x;i<=n;i+=i&-i)//向上查詢
ret+=e[i]; return ret; }
樹狀數組能夠有兩個方向,1.向下更新,向上查詢 2.向上更新,向下查詢。本題用的是向上更新,向下查詢。
注意:樹狀數組更新時是增長量,初始時候更新量就是它自己。
因此開始時利用更新函數,將每個點更新,以後只要輸出就好了。
代碼:
#include<stdio.h>
int a[100005]; int c[100005]; int n; int lowbit(int x) { return x&(-x); } void add(int x,int ad) { for(int i = x;i<=n;i+=lowbit(i)) { c[i]+=ad; } } int getsum(int x) { int ans = 0; for(int i = x;i>0;i-=lowbit(i)) { ans+=c[i]; } return ans; } int main() { scanf("%d",&n); for(int i = 1;i<=n;i++) { scanf("%d",&a[i]); add(i,a[i]); } int m; scanf("%d",&m); for(int i = 1;i<=m;i++) { char s[2]; int x,y; scanf("%s%d%d",s,&x,&y); if(s[0]=='C') { add(x,y-a[x]); a[x] = y; }else { printf("%d\n",getsum(y) - getsum(x-1)); } } return 0; }
樹狀數組其餘應用,以後會陸續補充。若對於其有新的理解,也會加入到其中。
更新時間(2018.12.6)