初識樹狀陣列

2021-08-27 21:33:28 字數 3009 閱讀 4148

針對乙個陣列,反覆的修改、求區間和,普通的寫法:

//修改

a[index] = new_value;

//求區間和

int sum = 0;

for (int i = begin; i <= end; i++)

這樣的寫法,若區間長度為m,求和次數為n, 時間複雜度o(mn);

樹狀陣列的寫法,就是為了優化此類問題,尤其是字首和問題。

針對字首和問題,首先舉個例子:若要求前7項和,我們可以轉為求sum(a[7]) + sum(a[5:6]) + sum(a[1:4]).(注:樹狀陣列起始為1)

為什麼求第7項要這樣拆分呢?(7)10=(111)2   7的二進位制中有三個1,由低位到高位一次為1,2, 4;所以我們把她拆分為區間長度為1,2,4的三段。即sum(1, 7) = (a[7]) + (a[6] + a[5]) + (a[4]  + a[3] + a[2] + a[1]) = sum(a[7]) + sum(a[5:6]) + sum(a[1:4])。

所以我們正式開始計算字首和的時候,我們把這些要用的單元準備好即可。如何準備呢?

設索引index二進位制的最低位表示n,那麼就從當前位置,取n個元素求和並儲存。

如:(7)10 = (111)2  那麼從a[7]開始求區間長度為1的和。

再比如:(6)10 = (110)2  6二進位制的最低位表示2,所以從a[6]開始求區間長度為2的和。

示意圖如下

a為原陣列,c即需要構造的樹狀陣列。

那麼這是又有乙個問題,如何快速確定x二進位制最低位所表示的數字。

首先要知道x 與 (x-1)二進位制的區別就是從x二進位制對低位的1開始,更低位的數字全部變化,如(12)10 = (1100)2 , (11)10=(1011)

所以當x & (x - 1)時,即構造了乙個特殊的數——x去掉最低位1的數字 (12)2 & (11)2 = 1000;

所以 x - (x & (x - 1)) 即為所求

int lowbit(int x)

//功能一樣

int lowbit2(int x) //二進位制補碼,負數的二進位制就是正數取反+1

有了lowbit()函式,就可以求可以字首和了

//k為前k項和, vectorv為已經構建好的樹狀陣列

int sum(int k, vectorv)

return 0;

}

好了,求和的部分就是上述**,但是如何修改呢。

因為我們有針對原陣列的樹狀陣列,所以當修改原陣列a時,要對應維護樹狀陣列v。

假設現在修改index=6處的資料加上delta,那麼樹狀陣列的v[6]肯定也要加上delta,因為前七項和已經改變delta,容易理解v[1]-v[5]不用改變。那麼v[6]之後的資料如何改變?v[7]需要變嗎?不需要,因為sum[1:7]=v[7] + v[6] + v[4] 前7項和的增量delta已經在v[6]中加過了。那麼v[8]需要改變嗎呢?sum[8] = v[8],顯然是需要修改的,因為v[8]僅僅是原陣列前8項的和,現在更新了,就要加上delta。

那麼什麼時候要加delta,設麼時候不加delta ,當樹狀陣列下標二進位制與原陣列下標二進位制位數對齊,原陣列二進位制中每乙個1在陣列二進位制下標中都有對應即不用加delta,反之則需要。

如 原陣列index = 6    二進位制 01 10      sum[1:6] = v[6] + v[4]       

樹狀陣列下標 = 7 二進位制  01 11      sum[1:7] = v[7] + v[6] + v[4]   當v[6]增加delta時,v[7]不再需要增加

樹狀陣列下標 = 8 二進位制   10 00      sum[1:8] = v[8]   求前八項和,就是v[8],所以為了應對修改,此時v[8]需要增加delta

樹狀陣列下標 = 14 二進位制 11 10      綜上,下標為14時,不需要加delta

如何快速計算哪個不需要加delta,哪個需要delta,設t = lowbit(index),index + (t - 1)是不會有任何進製的,所以二進位制中對應的1都沒有改變,但是index + t時,就產生了進製,需要修改,算出新的index時,以此類推。

如:index = 6, t = lowbit(6) = 2; index + 1時沒有進製,即index + 1 = (7)10 = (111)2     二進位制中的1與(110)2對應

index + 2時,產生進製  即index+2 = (8)10 =(1000)2    二進位制中的1與(100)2不對應

所以**如下:

void updata(vector&v, int k, int delta)

}

有了上述**,我麼就可以初始化一顆樹狀陣列

void init(vectora, vector&v)

}

整體**如下:

#include #include using namespace std;

int lowbit(int x)

int sum(vectorv, int k)

return sum;

}void update(vector&v, int k, int delta)

}void init(vectora, vector&v)

} int main()

init(a, v);

cout << sum(v, 3) << endl;

update(v, 2, 4);

cout << sum(v, 9) << endl;

return 0;

}

以上**為求字首和,求區間和sum[3:5] = sum[1:5] - sum[1:2]

初識樹狀陣列

描述 description 輸入乙個數列a1,a2 an 1 n 100000 在數列上進行m 1 m 100000 次操作,操作有以下兩種 1 格式為c i x,其中c為字元 c i和x 1 i n,x 10000 都是整數,表示把把a i 改為x 2 格式為q l r,其中q為字元 q l和r...

樹狀陣列1 樹狀陣列入門

仔細看一下,發現tree的每乙個節點的高度並不是隨意的,而是由它轉成二進位制之後末尾連續零的數量決定的,連續零的數量加1,就是高度,例如 3 11 零的數量為0,加1等於1,所以它的高度就是1 6 110 零的數量為1,加1等於2,所以它的高度就是2 8 1000 零的數量為3,加1等於4,所以它的...

樹狀陣列 瞎bb 樹狀陣列

樹狀陣列是乙個利用一維陣列和位運算組成的求解區間問題的高效資料結構,其構造如圖所示 首先,我們要用它解決單點修改 區間查詢的操作。根據這張圖我們建立乙個陣列bit,下標就是圖中顯示的十進位制數。bit i 就表示了圖中所示的一段區間的和,例如bit 6 sum 5,6 bit 4 sum 1,4 下...