二叉索引樹(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(intx)
對於每乙個結點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(intx)
return
res;
}
對於第一種操作又怎麼更新c陣列中的元素呢?
同樣有了它和父節點的關係,修改了乙個ai,我們從ci開始往左走,沿途修改所有節點對應的ci即可。具體實現如下:
int add(int x, intd) }
可以看出兩個操作的時間複雜度均為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 5using
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 34const
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 34const
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二叉索...