二叉索引樹(樹狀陣列)

2022-06-20 17:45:11 字數 3650 閱讀 5519

二叉索引樹(binary indexed tree),又叫樹狀陣列,主要是用於解決動態連續和查詢問題。

給定乙個n個元素的陣列a1,a2,....,an,你的任務是設計乙個資料結構,支援以下兩種操作。

對於正整數x,我們 定義lowbit(x)為想的二進位制表示式中最右邊所對應的值。比如由38288的二進位制是1001010110010000可知lowbit(38288) = 16。我們將從0到n-1這n個數取lowbit,將數值相同的節點放在同一行上就可以得到一棵bit樹,而且lowbit值越大,越靠近根。

根據整數補碼表示的原理得到計算lowbit的方法是

int lowbit(int

x)

對於每乙個結點i,如果它是左子結點,那麼它的父節點編號是i+lowbit(i);如果它是右子結點,那麼它的父節點編號是i-lowbit(i)。這樣我們可以構造乙個輔助陣列c,其中

ci = ai - lowbit(i) + 1 + ai - lowbit(i) + 2 +...+ ai 

也即c的每個元素都是a陣列中的一段連續和。

有了c陣列之後怎麼計算字首和呢?

有了它和父結點的關係,我們可以順著結點i往左走,將沿途的c累加起來即可。具體實現如下:

int sum(int

x)

return

res;

}

對於第一種操作又怎麼更新c陣列中的元素呢?

同樣有了它和父節點的關係,修改了乙個ai,我們從ci開始往左走,沿途修改所有節點對應的ci即可。具體實現如下:

int add(int x, int

d) }

可以看出兩個操作的時間複雜度均為o(logn)。預處理時將a和c陣列清空,然後執行n次add操作,總時間複雜度為o(n*logn)。

直接看一道例題1:la4329 ping pong

題意是有n個人,每個人都有乙個技能值,問能夠舉行多少場比賽,組成比賽的條件是三個人,兩個選手,乙個裁判,這個裁判必須住在兩個選手的中間,而且技能值必須也在兩者之間。

乍一看好像不是直接動態區間求和的題,我們仔細分析一下,設a1到ai-1有ci個小於ai,那麼就有i - 1 - ci個大於ai的數字,同樣道理設ai到an有di個比ai小,就有n-i-di個比ai大。然後根據乘法原理總的方法數等於ci * (n - i - di) + di * (i - 1 - ci)。現在問題的關鍵是怎麼求解ci和di。

其實我們將ai作為乙個下標,每次將這個位置上的值修改為1,然後查詢cai-1,也就是前i - 1項的和,就是前面有幾個比ai小的數。類似的倒序遍歷ai查詢,得到bi。最後乙個迴圈計算所有的比賽數。通過本題可以知道,樹狀陣列用於快速計算字首和,此處用於快速計算有多少個比某值小的數的個數,可以說是一種妙用吧。

1 #include 2 #include 3 #include 4 #include 5

using

namespace

std;67

const

int maxn = 20000 + 10;8

intn, a[maxn], c[maxn], d[maxn];910

struct

bit

18void

clear()

21int lb(int

k) 24

25void add(int x, int

d) 30}31

32int sum(int

x) 38

return

res;39}

40}f;

41int

main()

4252

53f.resize(maxa);

54f.clear();

55for(int i = 1; i <= n; i++)

59f.clear();

60for(int i = n; i >= 1; i--)

6465

long

long ans = 0;66

for(int i = 1; i <= n; i++)

69 printf("

%lld\n

", ans);70}

71return0;

72 }

例題2:hdu 1166 敵兵布陣

題意很簡單,動態查詢連續和。

1 #include 2 #include 3

4const

int maxn = 50000 + 10;5

intn, s[maxn];

6int lb(int

k) 9

10void add(int k, int

v) 15}16

17int sum(int

k) 23

return

res;24}

2526

intmain()

2740 printf("

case %d:\n

", ca++);

41while(scanf("

%s", op), op[0] != 'e'

) else

if(op[0] == 'a'

) else

if(op[0] == 's'

) 51}52

}53return0;

54 }

例題3:hdu 1394 minimum inversion number

給出乙個n的全排列,問通過迴圈移動組成的n個序列中形成的逆序對數最小是多少。

先看怎麼計算乙個序列的逆序對數,從序列的第乙個數開始,假如該數字是3,n=10,易得該數是第7大的數,我們使用樹狀陣列查詢之前加入的(雖然之前加入了0個數)數的前7項和,就是3的逆序對數。不要忘記的是查詢後在樹狀陣列中標記。這樣依次計算後可以的到整個序列的逆序對數。

如果對每乙個序列都這麼做,時間複雜度是o(n2logn),時間是不允許的。由前面的計算我們知道了第乙個序列的逆序對數,思考能不能從第乙個逆序對數中得出後面序列的逆序對數。

答案是可以的。第二組序列可以看成是第乙個數字放到了數列的末尾,首先我們可以按照之前的方法,計算出前n-num[i]大的和,就是新增加的逆序對數,那減少了多少呢?

其實就是移動到末尾的數字的大小。

1 #include 2 #include 3

4const

int maxn = 5000 + 10;5

intn, s[maxn], num[maxn];67

int lb(int

k) 10

11void add(int k, int

v) 16}17

18int sum(int

k) 24

return

res;25}

2627

intmain()

2834

35int tm = 0;36

for(int i = 0; i < n; i++)

40int ans =tm;

4142

for(int i = 0; i < n - 1; i++)

51 printf("

%d\n

", ans);52}

53return0;

54 }

二叉索引樹(樹狀陣列)

二叉索引樹 binary indexed tree,bit 動態連續和查詢問題,給定乙個n個元素的陣列a1,a 2,an。支援以下兩種操作 1.add x,d 操作 讓a x增加d。2.query l,r 計算al a l 1 ar。在學習二叉索引樹之前,需要先介紹lowbit.對於正整數x,我們定...

樹狀陣列(二叉索引樹)

樹狀陣列的原理介紹可見劉汝佳 演算法競賽入門經典 訓練指南 194頁,講的非常好 簡單自己對樹狀陣列的基本理解 我們原先不是把陣列看成一排嗎,但現在不是了,我們用乙個類似二叉樹的結構來儲存資料,存到c當中去,好好研究劉汝佳所畫的圖。下面說明基本應用 對於乙個n元素的陣列a n 可執行如下操作 add...

BIT二叉索引樹(樹狀陣列)

powered by phantom lsh將上面的求連續和問題稍微改進一下,現在需要支援一種新的操作 add x d 即把a x 增加d。這樣一來,如果通過字首和的方式計算就不能簡化計算了,因為每次修改乙個元素都要修改所有在它後面的字首和。有什麼解決辦法呢?我們需要用一種新的資料結構 bit二叉索...