Fenwick-Tree
Fenwick Tree
相較於前綴和只能用來查詢區間和
當我們想要修改某項數值時,就需要重新處理一次
前綴和的 O(1) 求區間和,無法滿足大量的修改操作
這時候可以使用 Fenwick Tree 來快速的修改、求總和
Fenwick Tree 可以在 O(n) 的時間初始化,並且在 O(log n) 的時間查詢區間和、修改其中一項元素的值
別名:Binary Indexed Tree、BIT、樹狀數組
要點
功能
- update:更新某項元素的值
- query:查詢某範圍的區間和
lowbit
lowbit 是整個資料結構的核心
假如 n =
這時候 n 可以拆分成
從二進制上可以直接理解為 10010 的 lowbit 是 10
觀念
我們會需要兩個一維陣列 v 跟 bit
v:原數列
BIT:預處理後的數列
最後可以藉由 bit 陣列求出前綴和並計算該區間和
因為位元計算 lowbit 及前綴求區間和,BIT 陣列也是 1-based
底下是 BIT 的圖解
圖片引用自 這裡
只要將 v 中的所有元素取 index 的二進制中所有為 1 的 bit
當作 BIT 的 index,將其值增加 v 的數值
就可以將 BIT 建表
實作
Fenwick Tree 會用到的變數、陣列
這次範例會開在全域,讓函式可以直接存取
n 為原陣列的長度
1
2
vector<int> v, BIT;
int n;lowbit
可以對相反數做 & 運算來實作 lowbit
且可以利用 define 來增加程式碼的可讀性
1
#define lowbit(x) ((x)&-(x))update 函式
原數列 v 的改變要映射到 BIT 數列
其實就是對受影響的區間增加 / 減少 v 的值
1
2
3
void upd(int idx, int val) {
for (; idx <= n; idx += lowbit(idx)) bit[idx] += val;
}如果想要更改某項 v 數列中的值
則可以刪除原本的值,再增加新的值 1
2
3upd(a, v[a]); v[a] = b; upd(a, b);
建表
而建表其實就是對在 v 中的每項數值
在 BIT 內更新一次
1
2
3
4
5
6
void build() {
for (int i = 1; i <= n; i++) {
cin >> v[i];
upd(i, v[i]);
}
}查詢
從 BIT 查詢區間和 [a, b],需要從 BIT 裡面取兩次前綴和
也就是 [1, b] 減去 [1, a-1]
1
2
3
4
5
6
int query(int a, int b) {
int sa = 0, sb = 0; a--;
for (; b > 0; b -= lowbit(b)) sb += bit[b];
for (; a > 0; a -= lowbit(a)) sa += bit[a];
return sb-sa;
}範例
題目:HDU
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) ((x)&-(x))
vector<int> v, bit;
int n;
void add(int idx, int val) {
for (; idx <= n; idx += lowbit(idx)) bit[idx] += val;
}
int get(int start, int end) {
int a = 0, b = 0;
start--;
for (; start > 0; start -= lowbit(start)) a += bit[start];
for (; end > 0; end -= lowbit(end)) b += bit[end];
return b-a;
}
signed main() { ios::sync_with_stdio(0); cin.tie(0);
int t, i;
for (cin >> t, i = 1; i <= t; i++) {
cout << "Case " << i << ":\n";
cin >> n;
v.resize(n);
for (auto &i : v) cin >> i;
bit.resize(n+1, 0);
for (int i = 0; i < n; i++) add(i+1, v[i]);
string cmd;
int a, b;
while (cin >> cmd) {
if (cmd == "End") break;
cin >> a >> b;
if (cmd == "Query") cout << get(a, b) << '\n';
else if (cmd == "Add") add(a, b);
else if (cmd == "Sub") add(a, -b);
}
v.clear(); bit.clear();
}
}用 BIT 求逆序數
逆序數
逆序數是指一個數列中,滿足 i < j 且第 i 個數小於第 j 個數的數對數量
簡單來說就是