樹狀數組——————二分索引樹


樹狀數組————BIT(Binary Index Tree),又稱二分索引樹。不得不承認,二分索引樹的名字更能反應它的本質,而樹狀數組這個名字則更加的直觀。

與其說是一種算法,不如說是一種數據結構。從名字可以直觀的看出,這是一種像是樹一樣的數組。這就具有很多優點,能夠在log(n)的時間內進行查詢、求和等操作。

下面看這張圖(提到樹狀數組必須離不開這個圖)


其中以A表示原數組,C表述樹狀數組。

下面給出一個構造的原則(或者說樹狀數組的每一個結點的含義吧):C[i] = A[i-2^k+1] + .........+A[i]

其中這個k的含義是什么呢?

即表示將i轉化為2進制后,從右往左數,0的個數。(大家大可不必問為什么,這個證明應該是數學問題了,找了一下沒有發現相關資料於是我就作罷了...)

比如說,i = 8

8的二進制表示為00001000,從右往左數到第一個1為止共有三個0,則k = 3

那么C[i]表示的是從A[8 - 2 ^ 3 + 1] 到 A[8]也即A[1......8]的值

下面給出求一個函數LowBit(int index)用來求2^k

相信這個地方還是很難理解,我舉幾個例子看看。

例如,index = 6 經過LowBit運算后得出結果為2那么C[6] = A[5] + A[6]

(k == 2 剛好是表示A[i-1 + 1] + A[i - 1 + 1]這兩個數的和,和前邊的構造原則是一致的)

有一個簡單的記憶方式,6的因子有1,2,3,6,這四個因子中有一個2是2的n次方,所以這個2就是我們想要求的那個k

在打一個比方,8的因子有1,2,4,8,其中有三個數可以表示成2的n次方(2,4,8),但那個最大的8是我們想要的k.

又如7,其因子1,7沒有一個數可以表示成2的n次方,得出的結果k = 1,那么,可以得出一致結論,當index為奇數時,C[i] = A[i]

(以上紅字部分可以加深對LowBit的理解)

<span style="font-family:Microsoft YaHei;font-size:14px;">int LowBit(int index)
{
	return index & (-index);
}</span>

(至於為什么這么算,可以看BYvoid的鏈接https://www.byvoid.com/blog/binary-index-tree)

要時刻明白,LowBit這個函數,求得的是index的所有因子中滿足以下條件的那個數值:

1.最大的因子

2.該因子可以表示成2的整數次冪。

巧合的是,這個LowBIt求得的值,就是當前結點的管轄范圍。

什么意思呢?

大家看上圖:同樣以index = 8 為例。index = 8的這個葉節點的管轄范圍是原數組中的1----8,這8個結點都可以看做是index結點的子結點

實際上,我們有這樣的重要定理:

index += LowBit(index)得到的值,正是子節點index的父節點。

如:index = 4時 index += LowBit(index)  = 8,也就是管轄4這個結點的父節點。

相應的index -= LowBit(index)可以得到index管轄的子節點。

下面看一個例子:

敵兵布陣

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 45999    Accepted Submission(s): 19553


Problem Description
C國的死對頭A國這段時間正在進行軍事演習,所以C國間諜頭子Derek和他手下Tidy又開始忙乎了。A國在海岸線沿直線布置了N個工兵營地,Derek和Tidy的任務就是要監視這些工兵營地的活動情況。由於采取了某種先進的監測手段,所以每個工兵營地的人數C國都掌握的一清二楚,每個工兵營地的人數都有可能發生變動,可能增加或減少若干人手,但這些都逃不過C國的監視。
中央情報局要研究敵人究竟演習什么戰術,所以Tidy要隨時向Derek匯報某一段連續的工兵營地一共有多少人,例如Derek問:“Tidy,馬上匯報第3個營地到第10個營地共有多少人!”Tidy就要馬上開始計算這一段的總人數並匯報。但敵兵營地的人數經常變動,而Derek每次詢問的段都不一樣,所以Tidy不得不每次都一個一個營地的去數,很快就精疲力盡了,Derek對Tidy的計算速度越來越不滿:"你個死肥仔,算得這么慢,我炒你魷魚!”Tidy想:“你自己來算算看,這可真是一項累人的工作!我恨不得你炒我魷魚呢!”無奈之下,Tidy只好打電話向計算機專家Windbreaker求救,Windbreaker說:“死肥仔,叫你平時做多點acm題和看多點算法書,現在嘗到苦果了吧!”Tidy說:"我知錯了。。。"但Windbreaker已經掛掉電話了。Tidy很苦惱,這么算他真的會崩潰的,聰明的讀者,你能寫個程序幫他完成這項工作嗎?不過如果你的程序效率不夠高的話,Tidy還是會受到Derek的責罵的.
 

Input
第一行一個整數T,表示有T組數據。
每組數據第一行一個正整數N(N<=50000),表示敵人有N個工兵營地,接下來有N個正整數,第i個正整數ai代表第i個工兵營地里開始時有ai個人(1<=ai<=50)。
接下來每行有一條命令,命令有4種形式:
(1) Add i j,i和j為正整數,表示第i個營地增加j個人(j不超過30)
(2)Sub i j ,i和j為正整數,表示第i個營地減少j個人(j不超過30);
(3)Query i j ,i和j為正整數,i<=j,表示詢問第i到第j個營地的總人數;
(4)End 表示結束,這條命令在每組數據最后出現;
每組數據最多有40000條命令
 

Output
對第i組數據,首先輸出“Case i:”和回車,
對於每個Query詢問,輸出一個整數並回車,表示詢問的段中的總人數,這個數保持在int以內。
 

Sample Input
  
  
  
          
1 10 1 2 3 4 5 6 7 8 9 10 Query 1 3 Add 3 6 Query 2 7 Sub 10 2 Add 6 3 Query 3 10 End
 

Sample Output
  
  
  
          
Case 1: 6 33 59
 
題目關鍵在於要不斷的修改某一點的值並且不斷的求取某一段區間的和,這正是樹狀數組的典型應用:求一段連續區間的和。

這里,再給出兩個重要的函數。

1.

____________________________________________________________________________________________________________________________________

<span style="font-family:Microsoft YaHei;font-size:14px;">void Update(int index,int value)
{
	while(index <= N){
		tree[index] += value;//這里不一定要加,可以給出必要的更改
		index += LowBit(index);
	}
}</span>

其中N是表示原數組的容量

(這個地方其實有一容易被忽略的地方,就是C語言數組下標一般從0開始。不過LowBit(0) == 0 ,那么在這個函數中 0 += LowBit(0) 會一直得到0的結果始終<=N就會變成死循環!所以在構造原數組的時候一定要注意!)

好了,下面說明一下這個函數的功能。

因為樹狀數組的特殊性——某一個結點的管轄結點可能有很多個,修改子節點的時候,必須上溯到它的父節點,這一條路徑的上的值都要更改,所以有了這個Update函數。

____________________________________________________________________________________________________________________________________

2.

<span style="font-family:Microsoft YaHei;font-size:14px;">int Sum(int index)
{
	int sum = 0;
	
	while(index){
		sum += tree[index];
		index -= LowBit(index);
	}
	return sum;
}</span>

函數很簡單,求index結點所對應的子節點和。

(其實就是從1----index的原數組的和)



看懂了圖就看懂了一大半,下面給出題目代碼,有興趣的可以研究一下

HDU 1166 

<span style="font-family:Microsoft YaHei;font-size:14px;">/*--------------------------------------------------------------------
12二進制:  00001100
    取反:             11110011 負數補碼形式哪負數補碼呢
我先看看負數補碼何表示【負數補碼其原碼逐位取反符號位除外;整數加1】
我返弄:
先11110011-1=11110010
符號位外取反:10001101 
看看除符號外數:0001101 13 所數-13
PS:
1 & (-1) == 1
*/
#include <stdio.h>
#include <string.h>
#define maxn 50005

int num[maxn];
int TreeC[maxn],N;//樹狀數組 
void Initialize()
{
	memset(TreeC,0,sizeof(TreeC));
	memset(num,0,sizeof(num));
}
int LowBit(int index)
{
	return index & (-index);
}
int Sum(int index)
{
	int ans = 0;
	while(index > 0){
		ans += TreeC[index];
		index -= LowBit(index);
	}
	return ans;
}
void Update(int index,int value)//維護樹狀數組 
{	
	while(index <= N){
		TreeC[index] += value;
		index += LowBit(index);
	}
}
int main()
{
	int Casenum,a,b;
	char str[10];
	
	scanf("%d",&Casenum);
	for(int cases = 1 ; cases <= Casenum ; ++cases){
		Initialize();
		scanf("%d",&N);
		bool over = false;
		for(int i = 1 ; i <= N ; ++i){
			scanf("%d",&num[i]);
			Update(i,num[i]);//讀取一組數構建一次樹狀數組 
		}
		printf("Case %d:\n",cases);
		while(scanf("%s",str) != EOF){
			switch(str[0]){
				case 'Q' : { scanf("%d%d",&a,&b) ; printf("%d\n",Sum(b)-Sum(a-1)) ; } ; break;
				case 'A' : { scanf("%d%d",&a,&b) ; Update(a,b)  ;} ; break;
				case 'S' : { scanf("%d%d",&a,&b) ; Update(a,-b) ;} ; break;
				case 'E' : over = true;break;
			}
			if(over)
				break;
		}
	}
	return 0;
}</span>



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM