ST 表是用于解决 可重复贡献问题 的数据结构,是一种离线算法。
可重复贡献问题:简单来说就是求max或min的顺序是不影响结果,每个值可以被多次包括、计算。
常见的可重复贡献问题有:区间最值、区间按位和、区间按位或、区间GCD等。
时间复杂度和空间复杂度
- 时间复杂度:
处理ST表:O(log2 n)
查询ST表:O(1) - 空间复杂度:O(n log n)
优缺点
- 优点: 时间复杂度低,代码量相对线段树少了不少。
- 缺点: 维护信息有限,拓展较少,修改困难。
核心代码
处理ST表(储存区间最大值)
void initSparseTable(vector<int> a) {
int la = a.size(), l2 = ceil(log2(la)) + 1;
for (int i = 0; i < la; ++i) ST[i][0] = a[i];
for (int j = 1; j < l2; ++j) {
int pj = 1 << (j - 1);
for (int i = 0; i + pj < la; ++i) {
ST[i][j] = max(ST[i][j - 1], ST[i + pj][j - 1]);
}
}
}
查询ST表(储存区间最大值)
int Query(int l, int r) {
int lt = r - l + 1;
int t = lt == 1 ? 0 : ceil(log2(lt)) - 1;
return max(ST[l][t], ST[r - (1 << t) + 1][t]);
}
反向ST表实现边建边查(储存最大值)
void UpdateSparseTable() {
ST[n][0] = a[n];
for (int j = 1; (1 << j) <= n; ++j) {
ST[n][j] = max(ST[n][j - 1], ST[n - (1 << (j - 1))][j - 1]);
}
}
int Query(int l, int r) {
int lt = r - l + 1;
int t = lt == 1 ? 0 : ceil(log2(lt)) - 1;
return max(ST[r][t], ST[l + (1 << t) - 1][t]);
}
练习题目
1.质量检测
题意描述
求区间最小值。
解题思路
ST表
AC代码
/**
* \link: https://www.luogu.com.cn/problem/P2251
* \category: ST表 SparseTable 单调队列
*
*
*
**/
// #pragma GCC optimize("O2")
#include <algorithm>
#include <iostream>
#include <cmath>
#include <deque>
#define DBG(x) cout << #x << " = " << (x) << '\n'
using namespace std;
/** using SparseTable. */
const int kN = 1e6 + 233;
vector<vector<int> > ST(kN, vector<int>(21));
void initSparseTable(vector<int> &data) {
int ld = data.size(), l2 = ceil(log2(ld)) + 1;
for (int i = 0; i < ld; ++i) ST[i][0] = data[i];
for (int j = 1; j < l2; ++j) {
int pj = 1 << (j - 1);
for (int i = 0; i + pj< ld; ++i) {
ST[i][j] = min(ST[i][j - 1], ST[i + pj][j - 1]);
}
}
}
int Query(int l, int r) {
int lt = r - l + 1;
int t = lt == 1 ? 0 : ceil(log2(lt)) - 1;
return min(ST[l][t], ST[r - (1 << t) + 1][t]);
}
inline void solve() {
int n, k; cin >> n >> k;
vector<int> data(n);
for (auto &it : data) cin >> it;
initSparseTable(data);
for (int i = k - 1; i < n; ++i) {
cout << Query(i - k + 1, i) << '\n';
}
return;
}
/** using monotonic queue. */
inline void solve2() {
int n, k; cin >> n >> k;
vector<int> data(n);
for (auto &it : data) cin >> it;
vector<int> ans;
deque<int> que;
for (int i = 0; i < n; ++i) {
while (que.size() && data.at(i) <= data.at(que.back())) que.pop_back();
while (que.size() && que.front() <= i - k) que.pop_front();
if (i >= k - 1) {
if (que.size()) ans.push_back(data.at(que.front()));
else ans.push_back(data.at(i));
}
que.push_back(i);
}
for (auto item : ans) cout << item << '\n';
}
bool rt = false;
signed main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("/Users/Shared/test.in", "r", stdin);
#endif
if (rt) { int T; cin >> T; while (T--) solve(); }
else solve();
return (0 ^ 0);
}
2. Balanced Lineup G
题意描述
求区间最大值减最小值
解题思路
双倍ST表
AC代码
/**
* \problem: P2880 [USACO07JAN] Balanced Lineup G
* \link: https://www.luogu.com.cn/problem/P2880
* \category: SparseTable 树状树组
* \date: Fri Jul 14 09:43:28 CST 2023 - Fri Jul 14 10:29:30 CST 2023
*
**/
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
const int kN = 2e5 + 233;
vector<vector<int> > ST1(kN, vector<int>(31));
vector<vector<int> > ST2(kN, vector<int>(31));
void initSparseTable(vector<int> data) {
int ld = data.size(), l2 = (int)ceil(log2(ld)) + 1;
for (int i = 0; i < ld; ++i) {
ST1[i][0] = data.at(i);
ST2[i][0] = data.at(i);
}
for (int j = 1; j < l2; ++j) {
int pj = 1 << (j - 1);
for (int i = 0; i + pj < ld; ++i) {
ST1[i][j] = max(ST1[i][j - 1], ST1[i + pj][j - 1]);
ST2[i][j] = min(ST2[i][j - 1], ST2[i + pj][j - 1]);
}
}
}
int Query(int l, int r, bool isMax) {
int lt = r - l + 1;
int t = lt == 1 ? 0 : (int) ceil(log2(lt)) - 1;
if (isMax) return max(ST1[l][t], ST1[r - (1 << t) + 1][t]);
else return min(ST2[l][t], ST2[r - (1 << t) + 1][t]);
}
inline void solve() {
int n, q; cin >> n >> q;
vector<int> data(n);
for (auto &it : data) cin >> it;
initSparseTable(data);
while (q--) {
int l, r; cin >> l >> r;
int maxx = Query(l - 1, r - 1, true);
int minn = Query(l - 1, r - 1, false);
// cout << "test:" << maxx << ' ' << minn << '\n';
cout << maxx - minn << '\n';
}
}
bool rt = false;
signed main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif
if (rt) { int T; cin >> T; while (T--) solve(); }
else solve();
return (0 ^ 0);
}
3.最大数
题目描述
向末尾添加元素的同时求区间最大值
解题思路
反向ST表实现
AC代码
/**
* \link: https://www.luogu.com.cn/problem/P1198
* \category: SparseTable
*
* \date:
**/
// #pragma GCC optimize("O2")
#include <algorithm>
#include <iostream>
#include <cmath>
#define DBG(x) cout << #x << " = " << (x) << '\n'
#define int long long
using namespace std;
const int kN = 2e5 + 233;
int n = 0;
vector<int> a(kN);
vector<vector<int> > ST(kN, vector<int>(31));
void UpdateSparseTable() {
ST[n][0] = a[n];
for (int j = 1; (1 << j) <= n; ++j) {
ST[n][j] = max(ST[n][j - 1], ST[n - (1 << (j - 1))][j - 1]);
}
}
int Query(int l, int r) {
int lt = r - l + 1;
int t = lt == 1 ? 0 : ceil(log2(lt)) - 1;
return max(ST[r][t], ST[l + (1 << t) - 1][t]);
}
inline void solve() {
int q, MOD; cin >> q >> MOD;
int t = 0;
while (q--) {
char ch; cin >> ch;
switch(ch) {
case 'A': {
int x; cin >> x;
a[++n] = (x + t) % MOD;
UpdateSparseTable();
break;
}
case 'Q' : {
int x; cin >> x;
cout << (t = Query(n - x + 1, n)) << '\n';
break;
}
}
}
}
bool rt = false;
signed main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif
if (rt) { int T; cin >> T; while (T--) solve(); }
else solve();
return (0 ^ 0);
}