樹狀陣列(binary index tree, bit)也是很多人心中最簡潔優美的資料結構之一。最簡單的樹狀陣列支援兩種操作,時間複雜度均為 :
當然,樹狀陣列能維護的不侷限於加法,支援的操作也不止這兩種,甚至有大佬能用樹狀陣列實現平衡樹,但這篇筆記不會深入討論(因為我也還不是很懂)。
回顧一下,我們說,我們要實現兩種操作:單點修改和區間求和。對於普通陣列而言,單點修改的時間複雜度是 o(1
)o(1)
o(1)
,但區間求和的時間複雜度是 o(n
)o(n)
o(n)
。當然,我們也可以用字首和的方法維護這個陣列,這樣的話區間求和的時間複雜度就降到了 o(1
)o(1)
o(1)
,但是單點修改會影響後面所有的元素,時間複雜度是o(n
)o(n)
o(n)
。程式最後跑多長時間,是由最慢的一環決定的,因此現在我們希望找到這樣一種折中的方法:無論單點修改還是區間查詢,它都能不那麼慢地完成。
注意到對[a,
b]
[a,b]
[a,b
]進行區間查詢只需查詢 [1,
b]
[1,b]
[1,b]和[1,
a)
[1,a)
[1,a
)然後相減即可(字首和就是這樣進行區間查詢的),所以我們可以把區間查詢問題轉化為求前n項和的問題。
關於陣列的維護,有個很自然的想法:可以用乙個陣列維護若干個小區間,單點修改時,只更新包含這一元素的區間;求前n項和時,通過將區間進行組合,得到從1到n的區間,然後對所有用到的區間求和。實際上,設原陣列是 a
aa,如果 c
ic_i
ci維護的區間是[ai
,ai]
[a_i,a_i]
[ai,a
i] ,此結構就相當於普通陣列(還浪費了一倍記憶體);如果c
ic_i
ci維護的區間就是[1,
ai
][1,a_i]
[1,ai
],此結構就相當於字首和。
現在我們試圖尋找一種結構,一方面,單點修改時需要更新的區間不會太多;另一方面,區間查詢時需要用來組合的區間也不會太多。
樹狀陣列就是這樣一種結構,它巧妙地利用了二進位制(實際上,樹狀陣列的英文名bit,直譯過來就是二進位制下標樹)。例如11,轉化為二進位制數就是 (
1011)2
(1011)_2
(1011)
2,如果我們要求前11項和,可以分別查詢 (
(0000)2
,(
1000)2
]((0000)_2,(1000)_2]
((0000
)2,
(100
0)2
]、 (
(1000)2
,(
1010)2
]((1000)_2,(1010)_2]
((1000
)2,
(101
0)2
]以及 (
(1010)2
,(
1011)2
]((1010)_2,(1011)_2]
((1010
)2,
(101
1)2
]的和再相加。這三個區間怎麼來的呢?其實就是不斷地去掉二進位制數最右邊的乙個1的過程。
我們定義,二進位制數最右邊的乙個1,連帶著它之後的0為 low
bit(
x)
lowbit(x)
lowbit
(x)(稍後再來看如何實現)。那麼我們用c
ic_i
ci維護區間((a
i−lo
wbit
(ai)
,ai]
((a_i-lowbit(a_i),a_i]
((ai−
lowb
it(a
i),
ai]
,這樣顯然查詢前n項和時需要合併的區間數是少於log2
n\log_2^n
log2n
的。那麼如何更新呢,大家會發現更新就是乙個「爬樹」的過程。一路往上更新,直到maxn(樹狀陣列的容量)。
每一步都把從右邊起一系列連續的1變為0,再把這一系列1的前一位0變為1。這看起來像是乙個進製的過程對吧?實際上,每一次加的正是 low
bit(
x)
lowbit(x)
lowbit
(x)。這樣,我們更新的區間數不會超過 log2
maxn
\log_2^
log2ma
xn。乙個能以 o
(logn
)o(\log n)
o(logn
)時間複雜度進行單點修改和區間查詢的資料結構就誕生了。
lowbit怎麼算?如果一位一位驗證的話,會形成額外的時間開銷。然而,我們有這樣神奇的乙個公式:
l ow
bit(
x)=(
x)
lowbit(x)=(x)
lowbit
(x)=
(x) & (−x
)(-x)
(−x)
我們需要知道,計算機裡有符號數一般是以補碼的形式儲存的。-x相當於x按位取反再加1,會把結尾處原來1000…的形式,變成0111…,再變成1000…;而前面每一位都與原來相反。這時我們再把它和x按位與,得到的結果便是low
bit(
x)
lowbit(x)
lowbit
(x)。
int tree[maxn]
;inline
void
update
(int i,
int x)
inline
intquery
(int n)
inline
intquery
(int a,
int b)
初始化的時候,我們只需要update每個點的初始值即可。
(hdu p1166)敵兵布陣
(洛谷p1908) 逆序對
學習筆記 樹狀陣列
以下 為樹狀陣列最常用的幾個操作 1.low bi t mathrm lowbit function lowbit x longint int64 begin exit x and x end 2.單點修改 procedure replace x,y int64 var i int64 begin ...
樹狀陣列學習筆記
在學習完了線段樹後,聽說樹狀陣列能寫的題,線段樹都能做,所以一直沒有詳細的學習樹狀陣列 直到碰到了一道卡線段樹的題目,因為線段樹運用了很多遞迴,所以常數比較大,容易被卡 現在總結一下樹狀陣列 1 樹狀陣列個人認為就是字首和演變而來的 2 單點更新 當你要更新某個點的值時,你要從下面到上面依次更新過去...
樹狀陣列學習筆記
參考自 0.介紹 來自wikipedia 樹狀陣列,又稱二分索引樹 binary indexed tree,bit 用於高效計算數列的字首和.它可以以 的時間得到 並同樣以 對某項加乙個常數。1.資料結構定義 存放原始資料元素的陣列a i.e.int a 樹狀陣列 s i.e.int s 注意 原始...