## 介紹
Sparse Tabel 是一種支援在**靜態**情況下 O(1) 查詢一段區間的最大或最小值的資料結構。
## 建立 Sparse Table
???+note "[CSES - Static Range Minimum Queries]()"
給一個陣列,q 次詢問:
- $\text{query}(l, r):$ 查詢 $a_l, ..., a_r$ 內的最小值
$n, q \le 2 \times 10^5, 1 \le a_i \le 10^9$
我們使用倍增法,令 st(i, j) = 從 a[j] ~ a[j + 2i - 1] 的最小值。跟 lca 很像,我們可以列出轉移式
- st(0, j) = a[j]
- st(i, j) = min(st(i - 1, j), st(i - 1, j + 2i))
以下是 Sparse Table 的實作。
??? note "code"
```cpp linenums="1"
void build () {
int lg = std::__lg(n);
for (int i = 1; i <= n; i++) par[0][i] = a[i];
for (int i = 1; (1 << i) <= n; i++) {
for (int j = 1; j + (1 << i) - 1 <= n; j++) {
par[i][j] = min (par[i - 1][j], par[i - 1][j + (1 << (i - 1))]);
}
}
}
```
## 查詢最小值
令 i = log(r - l + 1),從 l 開始到後面長度為 2i 的與從 r 開始往前長度為 2i 的這兩個 interval 聯集起來的最小即為所求。因為從左右兩端 log(r - l + 1) 的長度如果沒有 overlap,一定至少會覆蓋整個區間,所以是可行的。
但若嫌每次計算 log 太花時間,我們也可以對 log 也進行的預處理:
??? note "code"
```cpp linenums="1"
for (int i = 2; i <= n; i++) {
log_value[i] = log_value[i / 2] + 1;
}
```
query 的程式碼如下:
??? note "code"
```cpp linenums="1"
int query (int l, int r) {
int lg = std::__lg(r - l + 1);
return min (par[lg][l], par[lg][r - (1 << lg) + 1]);
}
```
### 模板
???+note "code"
```cpp linenums="1"
int par[21][maxn];
int a[maxn];
void build () {
int lg = std::__lg(n);
for (int i = 1; i <= n; i++) par[0][i] = a[i];
for (int i = 1; (1 << i) <= n; i++) {
for (int j = 1; j + (1 << i) - 1 <= n; j++) {
par[i][j] = min (par[i - 1][j], par[i - 1][j + (1 << (i - 1))]);
}
}
}
int query (int l, int r) {
int lg = std::__lg(r - l + 1);
return min (par[lg][l], par[lg][r - (1 << lg) + 1]);
}
```
## 例題
???+note "模板測試 [CSES - Static Range Minimum Queries](https://cses.fi/problemset/task/1647)"
給一個長度為 $n$ 的陣列 $a_1,\ldots ,a_n$,$q$ 次詢問 :
- $\text{min}(l,r):$ $\{a_l,\ldots ,a_r\}$ 的最小值
$n,q\le 2\times 10^5,1\le a_i\le 10^9$
???+note "[CF 1904 D2. Set To Max (Hard Version)](https://codeforces.com/problemset/problem/1904/D2)"
給兩個序列 $a,b$,問要進行幾次以下操作才能讓 $a=b$:
- $\text{update}(l,r):$ 讓所有 $a_l,\ldots ,a_r$ 都改成 $\max\{ a_l,\ldots ,a_r\}$
$n\le 2\times 10^5, 1\le a_i, b_i\le n$
??? note "思路"
考慮能變得 interval 要符合什麼條件,假設當前要改 $a_i$,則我們要找到最近的 $j$ 滿足 $b_i=a_j$,而且對於 $i\le k\le j$:
- 不能將 interval 內的項改的比他的 threshold 還小,也就是 $b_i\le b_k$
- $a_j$ 要是 interval $[i,j]$ 內的最大值,也就是 $a_k\le b_i$
所以我們從 $i=1\ldots n$,若當前 $a_i\neq b_i$,則將依照上面改即可,優化用 Sparse Table
???+note "[2021 附中模競 II pD. 調色盤 (Palette)](https://drive.google.com/file/d/1Qw4eUf0uSrLDOsdrq11xZxrCAVubAW4P/view)"
給一個長度為 $n$ 的陣列 $a_1 ,\ldots ,a_n$,有 $q$ 筆詢問如下 :
- $\text{query}(l,r):$ 問 $a_l \sim a_r$ 裡有幾個 subarray 滿足最大最小差 $\le k$
$n,k\le 10^6,c_i\le 10^6,q\le 10^6$
??? note "思路"
對於每個 i 定義 last[i] = 從 i 開始最大可以到哪個 index 滿足區間 max - min <= k
這可以用 two pointer + sparse table 預處理
然後對於 query(l, r) 就可以二分搜最大的分界點 t,滿足前面的 last[i] 都 <= r,後面的都 > r。前面的可以對於 last[ ] 維護 prefix sum,後面用數學解 O(1) 算即可
## Disjoint Sparse Table
query[l, r]
建立 log n 個 level
level[i] 處理 l 和 r 最高位相異 bit 是 i 的 query