---
tags: 進階班
---
# 樹狀數組 Binary Index Tree (BIT)
## 用途
在要使用線段樹的狀況下,某些題目可以用寫起來較快的 BIT 實作,提升速度。
除此之外,BIT 記憶體較小,在某些線段樹空間會爆的情況下可使用
## 結構
先畫個示意圖:

從這個圖可以很直觀的看出:**用 BIT 做 `query`,可以把 `arr[1] ~ arr[n]` 很輕鬆的求出來。**
\
而它其實是:對於每個節點 $n$,它存著陣列 $(n - lowbit(n),\; n]$ 的值。
**lowbit 是甚麼?**
對於每個數,其 lowbit 值為「數字轉為二進位後,位元為 $1$ 的最小位元所代表之值」
舉例:$3_{(10)}\rightarrow 011_{(2),}\;lowbit = 1,\quad 6_{(10)}\rightarrow 011 0_{(2)},\;lowbit = 2$
所以 `bit` 的 `query(1, n)` 時間複雜度是多少呢?
:::spoiler `隨便帶個數字想想看`
\
是 $O(lgN)$ 喔,跟線段樹一樣。
設 $x = 2^a + 2^b + 2^c + \;...\;\;(a > b > c > \;...)$
則 `query(x) = ... + bit[2^a + 2^b + 2^c] + bit[2^a + 2^b] + bit[2^a]`
:::
## 操作
1. **區間查詢 & 單點修改**
2. **單點查詢 & 區間修改**
如果你要 單點查詢 & 單點修改,請用一般陣列就好
如果你要 區間查詢 & 區間修改,請用線段樹 + 懶標記
## 實作
### 通則
先宣告 `BIT, arr`,再訂下陣列大小 `maxn`
```cpp=
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 2e5;
LL bit[maxn + 1], arr[maxn + 1];
```
$lowbit$ 其實可以用函數獲得:
```cpp=6
int lowbit(int x) {return x & -x;}
```
可以自己證明看看 :poop:
要做 `build()` 時
```cpp=7
void build(){
for(int x = 1; x <= n; x++) {
int k = x;
while(k <= maxn) bit[k] += arr[x], k += lowbit(k);
}
}
```
觀察上方的 bit 示意圖可得知,對於一個 `arr[x]`,若 `bit[k]` 存著 `arr[x]`,則 `k = 離 x 最近的 2 的倍數、4 的倍數、8 的倍數...`
前提是 `k >= x`。
而 2 的倍數、4 的倍數、8 的倍數... 它們最低的 $lowbit$ 是 2, 4, 8...
那麼上方的 `build()` 就出來了!
要做 `query(n)` 時
```cpp=13
LL query(int x){
while(x) {
return query(x - lowbit(x)) + bit[x];
}
return 0LL; //if(x == 0)
}
```
可以回上面翻「想想看」
### 細節
1. 區間查詢 & 單點修改
不須做任何特殊處理,直接實作
```cpp=19
void update(int i, LL x){ // 相當於 arr[i] += x
while(i <= maxn) bit[i] += x, i += lowbit(i);
}
int main(){
int n;
cin >> n;
for(int x = 1; x <= n; x++) cin >> arr[x];
build();
int ques, op, l, r, i, x;
cin >> ques;
while(ques--){
cin >> op;
if(op) cin >> l >> r, cout << query(r) - query(l - 1) << '\n';
else cin >> i >> x, update(i, x);
}
return 0;
}
```
2. 單點查詢 & 區間修改
`arr[i] -= arr[i - 1]`,存相鄰兩項差值
這樣區間和時,僅 `arr[l]` 及 `arr[r + 1]` 有改變。
```cpp=19
void update(int l, int r, LL x){
while(l <= maxn) bit[l] += x, l += lowbit(l); //update bit[l] - bit[l - 1]
while(r <= maxn) bit[r] -= x, r += lowbit(r); //update bit[r + 1] - bit[r]
}
int main(){
int n;
cin >> n;
for(int x = 1; x <= n; x++) cin >> arr[x];
for(int x = n; x; x--) arr[x] -= arr[x - 1];
build();
int ques, op, l, r, i, x;
cin >> ques;
while(ques--){
cin >> op;
if(op) cin >> i, cout << query(i) << '\n';
else cin >> l >> r >> x, update(l, r + 1, x);
}
return 0;
}
```
## 相關題目
[a477: March's New Textbook](http://203.64.191.163/ShowProblem?problemid=a477)
## 小結
基本上,BIT 的題目都可以拿線段樹來解,只是有可能:
1. 空間爆掉
2. 寫起來較為冗長,比較有可能出現 bug
但多知道一個解法,總比時間緊迫時來不及刻線段樹好
那麼,有甚麼線段樹題是 BIT 解不了的呢?
由於 BIT 只能 $query(1, \;n)$,只要做不到 $query(l, \;r) = query(1, \;r)$ 和 $query(1,\; l - 1)$ 逆運算的題目都不行。
e.g. 區間最大、最小值