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