樹狀陣列(Binary Indexed Tree)

2022-06-19 14:09:14 字數 3990 閱讀 9027

樹狀陣列(binary indexed tree,bit) 是能夠完成下述操作的資料結構。

給乙個初始值全為 0 的數列 a1, a2, ..., an

(1)給定 i,計算 a1+a2+...+ai

(2)給定 i 和 x,執行 ai += x

1.基於線段樹的實現

如果使用線段樹,只需要做少許修改就可以實現這兩個功能。線段樹的每個節點上維護的是對應區間的和。

接下來看如何計算從 s 到 t 的和(as + as+1 + ... + at)。在基於線段樹的實現這個和是可以直接求得的。

但是如果我們能夠計算(從 1 到 t 的和) - (從 1 到 s - 1 的和),同樣可以得到 s 到 t 的和。也就是說,只要對於任意 i,我們都能計算出 1 到 i 的部分和就可以了。

可以發現,線段樹上的每個節點的右兒子的值都不需要了(在計算時如果要使用這個點的值,那麼它的左邊的兄弟的值也一定會用到,這個時候只需要使用它們的父親的值就可以了)。

基於上面的思路得到的資料結構就是 bit。比起線段樹,bit實現起來更方便,速度也更快。

2. bit的結構

bit 使用陣列維護下圖所示的部分和

也就是把線段樹中不需要的節點去掉之後,再把剩下的節點對應到陣列中。對比每個節點對應的區間的長度和節點編號的二進位制表示。以 1 結尾的 1, 3, 5, 7 的長度是1, 最後有 1 個 0 的2, 6 的長度是2,最後有兩個 0 的 4 的長度是 4 .... 這樣,編號的二進位制表示就能夠和區間非常容易地對應起來。利用這個性質,bit 可以通過非常簡單的位運算實現。

3. bit的求和

計算前 i 項的和需要從 i 開始,不斷把當前位置 i 的值加入到結果中, 並從 i 中減去 i 的二進位制最低非 0 位對應的冪,直到 i 變為 0 為止。i 的二進位制的最後乙個 1 可以通過 i & - i 得到。

4.bit的值的更新

使第 i 項的值增加 x 需要從 i 開始,不斷把當前位置 i 的值增加 x, 並把 i 的二進位制最低非 0 位對應的冪加到 i 上。

5. bit的複雜度

總共需要對o(log n)個值進行操作,所以複雜度是o(log n)。

6. bit的實現

提一下 i -= i & -i 可以寫為 i = i &(i - 1)

//

[1, n]

int bit[max_n + 1

], n;

int sum(int

i)

return

s;}

void add(int i, int

x) }

//本來想把**補完整的。。參考線段樹就行,但是後面會有綜述我就不補了

7.  二維bit

bit 可以方便地擴充套件到二維的情況。對於 w * h 的二維 bit 只需建立 h 個大小為 x 軸方向元素個數 w 的 bit,然後把這些 bit 通過 y 軸方向的 bit 管理起來就可以了。也就是說,y 軸方向的 bit 的每個元素不是整數,而是乙個 x 軸方向的 bit。這樣所有的操作的複雜度都是 o(log w * log h)。用同樣的方法可擴充套件到更高的維度。

// emm 

8. 需要運用 bit 的問題

氣泡排序的複雜度是 o(n2),所以無法模擬過程。選用適當的資料結構可以解決這個問題。

所求的交換次數等價於 滿足 i < j , ai > aj  的 i  的個數,(這種數對的個數叫做逆序數)。而對於每乙個 j,如果能夠快速求出滿足 i < j, ai > aj  的 i  的個數,那麼問題就解決了。我們構建乙個值的範圍是 1 ~ n 的 bit,按照 j = 0, 1, 2, ..., n-1 的順序進行如下操作。

(1)把 j - (bit查詢得到的前 aj 項的和)加到答案中

(2)把 bit 中 aj 位置上的值加 1

對於每乙個 j, (bit查詢得到的前 aj 項的和)就是滿足i < j , ai > aj  的 i  的個數。因此把這個值從 j 中減去之後,得到的就是滿足 i < j , ai > aj  的 i  的個數。由於對於每乙個 j 的複雜度是o(log n),所以整個演算法的複雜度是o(n log n)。

// 但實際上實現計算逆序數更簡單的演算法是通過分治思想利用歸併排序計算

typedef long

long

ll;int

n, a[max_n];

//省略bit部分**

樹狀陣列可以高效地求出連續的一段元素之和或者更新單個元素的值。但是無法高效地給某乙個區間裡的所有元素同時加上乙個值。因此,本題無法直接使用樹狀陣列。在這裡先考慮利用線段樹來求解,然後再考慮改造樹狀陣列來求解。

對於每個節點,維護有該節點對應的區間的和,那麼就可以在 o(log n)時間內求出任意區間的和。但這樣就沒有辦法高效地實現對乙個區間同時加乙個值,因為需要對這個區間相關的所有節點都進行更新才可以。

為了保持線段樹的高效,對於每個節點我們維護以下兩個資料

(1)給這個節點對應的區間內的所有元素共同加上的值

(2)在這個節點對應的區間中除去(1)之外其他的值的和

通過單獨維護共同加上的值,給區間同時加上乙個值的操作就可以高效地進行了。如果對於父親節點同時加了乙個值,那麼這個值就不會在兒子節點被重複考慮。在遞迴計算和時再把這一部分的值加到結果裡面就可以了。這樣,不論是同時加乙個值還是查詢一段的和的複雜度都是o(log n)。

(1)t 是操作的種類。第 i 個操作的 t[ i ] 是 c 的話,就是給區間同時加上乙個值,是 q 的話則是查詢一段的和。

(2)a, l, r 都是以 0 為下標起點的。

typedef long

long

ll;const

int dat_size = (1

<< 18) - 1

;int

n, q;

inta[max_n];

char

t[max_q];

intl[max_q], r[max_q], x[max_q];

//線段樹

ll data[data_size], datb[max_size];

//對區間[a, b]同時加 x

//k 是節點的編號,對應的區間是(l, r)

void add(int a, int b, int x, int k, int l, int

r) }

//對區間[a, b]同時加 x

//k 是節點的編號,對應的區間是(l, r)

ll sum(int a, int b, int k, int l, int

r) }

void

solve()

}

未完待續

樹狀陣列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 下...

樹狀陣列 二維樹狀陣列模板

樹狀陣列模板 int lowbit int x int add int x,int val int que int x 模板題 題解 include include include using namespace std int c 300000 rank 300000 int n int lowb...