Data Structure
資料結構
INDEX
Review
Sparse Table
Binary Indexed Tree
Segment Tree
簡報自己讀不懂很正常他偏抽象
Review
預習
1. 區間查詢
2. 前綴和
Range Queries
區間查詢
問\ [l,\, r]\ 間\ 最小的值
最大的值
的和
\vdots
1
3
4
8
6
1
4
2
0
1
2
3
4
5
6
7
Range Sum
區間和
for (int i = l; i <= r; i++) {
sum += arr[i];
}
複雜度\ O(n)
q\ 次查詢 \rightarrow O(nq)
Range Sum
區間和
更快的寫法?
先計算從頭到每一個位置的加總
\Rightarrow 前綴和
\text{sum}(l, r) = \text{sum}(0, r) - \text{sum}(0, a - 1)
複雜度\ O(1)
1
4
8
16
22
23
27
29
0
1
2
3
4
5
6
7
Range Sum
區間和
定義\ S(X)\ 為左上角到\ X\ 的加總
則灰色區域和為\ S(A) - S(B) - S(C) + S(D)
\text{2D 前綴和}
D
C
B
A
Range Sum
區間和
剛剛的前綴和是\ O(n)\ 修改\ O(1)\ 查詢
如果會修改很多次\Rightarrow 用差分
對於一次修改\ [l, r]\ 可以視為在\ l\ 加上該值並且\ r + 1\ 時再減去
+3
-3
最後再做一次前綴和就可以達成\ O(1)\ 修改\ O(n)\ 查詢單點值
0
0
0
3
3
3
3
0
0
1
2
3
4
5
6
7
0
6
0
3
0
-6
0
-3
0
1
2
3
4
5
6
7
+6
-6
0
6
6
9
9
3
3
0
0
1
2
3
4
5
6
7
題目
Sparse Table
稀疏表
Minimum Queries
Minimum Queries
區間最小值
O(n \log n)\ 預處理\rightarrow 計算所有\ r - l + 1\ 為二的冪次的\ \text{min}(l, r)
\text{min}(l, r) = \text{min}(\text{min}(l, l + k - 1), \text{min}(l + k, r))
設\ k = (r - l + 1) / 2
1
3
4
8
6
1
4
2
0
1
2
3
4
5
6
7
0
1
2
3
4
5
6
7
l
l
r
0
1
2
3
4
5
6
7
\text{min}(l, r)
1
3
4
8
6
1
4
2
0
1
2
3
4
5
6
l
l
r
1
2
3
4
5
6
7
\text{min}(l, r)
1
3
4
6
1
1
2
0
1
2
3
4
l
l
r
3
4
5
6
7
\text{min}(l, r)
1
3
1
1
1
0
7
1
Minimum Queries
區間最小值
O(n \log n)\ 預處理完就可以\ O(1)\ 查詢了
設\ k\ 為不超過\ r - l + 1\ 的最大二冪次
\Rightarrow \text{min}(l, r) = \text{min}\left(\text{min}(l, l + k - 1), \text{min}(r - k + 1, r)\right)
1
3
4
8
6
1
4
2
0
1
2
3
4
5
6
7
1
3
4
8
6
1
4
2
0
1
2
3
4
5
6
7
1
3
4
8
6
1
4
2
0
1
2
3
4
5
6
7
Implement
如果要求最大值就把它改成\ \text{max}\ 就好
vector<vector<int>> st(__lg(n) + 1, vector<int>(n));
for (int i = 0; i < n; i++) st[0][i] = arr[i];
for (int i = 1; i <= __lg(n); i++) {
int len = 1 << (i - 1);
for (int j = 0; j < n; j++) {
st[i][j] = min(st[i - 1][j], st[i - 1][j + len]);
}
}
\text{Build}
int query(int l, int r) {
int k = __lg(r - l + 1);
return min(st[k][l], st[k][r - (1 << k) + 1]);
}
\text{Query}
題目
Binary Indexed Tree
樹狀陣列(二元索引樹)
Dynamic Range Sum Queries
Range Sum
區間和
但如果要改值就需要\ O(n)\ 的時間重新建構整個陣列
1
3
4
8
6
1
4
2
1
3
4
6
7
8
2
5
剛剛講過可以用前綴和
1
4
8
16
22
23
27
29
1
2
3
4
5
6
7
8
Binary Indexed Tree (BIT)
aka Fenwick Tree
設\ p(k)\ 為可以整除\ k\ 的最大二冪次
使\ tree[k] = sum(k - p(k) + 1, k)
1
3
4
8
6
1
4
2
雖然說樹但他其實只是個陣列
1
2
3
4
5
6
7
8
1
4
4
16
6
7
4
29
就可以構成這個陣列
1
2
3
4
5
6
7
8
Binary Indexed Tree (BIT)
把它畫成對應到原陣列的和
1
4
4
16
6
7
4
29
1
2
3
4
5
6
7
8
1
3
4
8
6
1
4
2
Binary Indexed Tree (BIT)
查詢\ [1, 7]
\text{sum}(1, 7) = \text{sum}(1, 4) + \text{sum}(5, 6) + \text{sum}(7, 7) = 16 + 7 + 4 = 27
查詢複雜度\ O(\log n)
1
4
4
16
6
7
4
29
1
2
3
4
5
6
7
8
Binary Indexed Tree (BIT)
改值也可以在\ O(\log n)\ 的時間內處理完
改位置為\ 3\ 的值
1
4
4
16
6
7
4
29
1
2
3
4
5
6
7
8
Implement
首先要知道怎麼計算\ p(k)
就神奇的發現\ p(k) = lowbit(k)\ 了
問題就變成怎麼計算\ lowbit
設\ p(k)\ 為可以整除\ k\ 的最大二冪次\\
使\ tree[k] = sum(k - p(k) + 1, k)
6 = 00110_{2}
k
p(k)
2 = 00010_2
7=00111_2
1 = 00001_2
12=01100_2
4 = 00100_2
Implement
繼續觀察
很愉快的又發現了\ lowbit(k) = k \text{\&} -\!k
6 = 00110_{2}
k
-k
2 = 00010_2
7=00111_2
1 = 00001_2
12=01100_2
4 = 00100_2
lowbit(k)
-6 = 11010_{2}
-7 = 11001_{2}
-12 = 10100_{2}
Implement
void add(int k, int x) {
while (k <= n) {
tree[k] += x;
k += k & -k;
}
}
\text{Modify}
int sum(int k) {
int ret = 0; // return
while (k) { // while k >= 1
ret += tree[k];
k -= k & -k;
}
return ret;
}
\text{Query}
要注意他是\ \text{1-based}\ 不然\ k = k \text{\&} -\!k\ 他不會變
Other Technique
如果操作改成差分
達成\ O(\log n)\ 區間改值\ O(\log n)\ 單點查詢
剛剛講的是單點改值區間查詢
\Rightarrow value(x) = sum(1, x)
1
4
4
16
6
7
4
29
1
2
3
4
5
6
7
8
Other Technique
當然也有區間改值區間查詢
講了單點改值區間查詢和區間改值單點查詢
設原數列\ A\ 以及差分陣列\ D,得出\ [1, x]\ 區間和為
\sum_{i=1}^{x} A_i= D_1 \times x + D_2 \times (x - 1) + \cdots + D_x \times 1= \sum_{i = 1}^{x}D_i \times (x - i + 1)
拆開得到
(x + 1)(\sum_{i =1}^{x}D_i) - (\sum_{i=1}^{x}D_i \times i)
開兩個\ \text{BIT}\ 一個維護\ D_i\ 一個維護\ D_i \times i
\Rightarrow O(\log n)\ 區間修改\ O(\log n)\ 區間查詢
題目
Segment Tree
線段樹
Dynamic Range Queries
Segment Tree
線段樹
能滿足大多區間操作和查詢的資料結構
除了寫起來麻煩沒有啥缺點了
以下為一顆求和的線段樹
每個節點對應到某個二的冪次的區間
39
22
17
5
8
6
3
2
7
2
6
13
9
9
8
arr
Implement
看起來很簡單但要怎麼寫
把它重新編號
肉眼可見\ tree[k] = tree[k \times 2] + tree[k \times 2 + 1]
39
22
17
5
8
6
3
2
7
2
6
13
9
9
8
8
9
10
11
12
13
14
15
1
2
3
4
5
6
7
Implement
tree[k]\ 還是等於\ tree[k \times 2] + tree[k \times 2 + 1]
算最小值的話\\
tree[k] = min(tree[k \times 2], tree[k \times 2 + 1])
5
8
6
3
2
7
2
6
8
9
10
11
12
13
14
15
塞到一維陣列裡面
39
22
17
13
9
9
8
1
2
3
4
5
6
7
Implement
39
22
17
5
8
6
3
2
7
2
6
13
9
9
8
看起來很簡單但要怎麼寫
8
9
10
11
12
13
14
15
1
2
3
4
5
6
7
求原陣列區間\ sum(1, 8)
l=9,\, r=15
l=5,\, r=7
l=3,\, r=3
8
9
17
=8 + 9 + 17 = 34
複雜度\ O(\log n)
Segment Tree
當前節點除以二就是父節點
複雜度\ O(\log n)
39
22
17
5
8
6
3
2
7
2
6
13
9
9
8
修改時要修改包含他的節點
8
9
10
11
12
13
14
15
1
2
3
4
5
6
7
Implement
迭代式
void add(int k, int x) {
k += n; // 先移到對應位置
tree[k] += x;
for (k /= 2; k > 0; k /= 2) {
tree[k] = tree[2 * k] + tree[2 * k + 1];
}
}
\text{Modify}
int sum(int l, int r) {
l += n, r += n;
int ret = 0;
while (l <= r) {
// 如果父節點會超出範圍就縮進來
if (l % 2 == 1) ret += tree[l++];
if (r % 2 == 0) ret += tree[r--];
l /= 2, r /= 2; // 移動到上一層
}
return ret;
}
\text{Query}
Implement
遞迴式
struct segmentTree {
int n;
segmentTree(vector<int> data) : n(data.size()) {
data.resize(n * 4);
build(1, 1, n, data);
}
void build(int id, int l, int r, vector<int> data) {
if (l == r) {
tree[id] = data[l];
return;
}
int mid = (l + r) / 2;
build(id * 2, l, mid, data);
build(id * 2 + 1, mid + 1, r, data);
tree[id] = tree[id * 2] + tree[id * 2 + 1];
}
void add(int id, int k, int x, int l, int r) {
if (l == r) {
tree[id] += x;
return;
}
int mid = (l + r) / 2;
if (k <= mid) modify(id * 2, k, x, l, mid);
else modify(id * 2 + 1, k, x, mid + 1, r);
tree[id] = tree[id * 2] + tree[id * 2 + 1];
}
int sum(int id, int l, int r, int L, int R) {
if (l <= L && R <= r) {
return tree[id];
}
int mid = (l + r) / 2;
int ret = 0;
if (L <= mid) ret += query(id * 2, l, mid, L, R);
if (R > mid) ret += query(id * 2 + 1, mid + 1, r, L, R);
return ret;
}
};
大小要開\ n \times 4
題目
掰掰
Sparse Table & BIT & Segment Tree
By keaucucal
Sparse Table & BIT & Segment Tree
- 110