ST表 总结
倍增基本思想
倍增是一种通过预先计算和存储指数级间隔的信息,将线性时间操作优化为对数时间的技术。核心思想是"以2的幂次为步长"进行跳跃或查询。
ST表
概念
ST 表是一种用于解决 RMQ (区间最值查询) 问题的数据结构,通过预处理 \(O(n\log_2n)\) 时间,支持 \(O(1)\) 查询。
优点:单次询问是 \(O(1)\) 的
缺点:无法在低时间复杂度修改 \(ST\) 表
步骤
像区间最值这样的问题,我们把他们称为可重复贡献问题,
可重复贡献问题就是指 \(x\) 和 \(x\) 做运算得到的结果还是 \(x\),如 \(\min(x, x) = x\),\(\gcd(x, x) = x\) 等。
预处理
设 \(f_i,_j\) 为区间 \([i, i + 2^j)\) 的最大值。
\(f_i,_j\) 可以由 \(f_{i},_{j-1}\) (跳 \(2^{j-1}\) 步)和 \(f_{i+2^{j-1}},_{j-1}\) (再跳 \(2^{j-1}\) 步)转移过来。
这就是倍增。
请把指数写前面!!!!!!!
查询
- 暴力查询
可以得知,任何数都可以拆分成若干个 \(2^k\) 的长度,每次暴力跳跃即可。
时间复杂度(\(q\) 次询问):\(O(qk\log_2n)\)。
和莫队一样,k 一般取 1,但如果是 \(\gcd\) 就不好说了!
- \(O(1)\) 查询
ST 表的核心是,任意一个区间都可以表示成至多两个长度为 \(2^k\) 的区间的并集。
因为交集的部分多次运算还等于本身,所以凑出的两个区间即使有交集,也不会影响最终的答案。
处理出倍增数组之后,对于任意一个区间查询,我们都可以找到两个已经预处理好结果的区间的并集等于原区间,所以对这两个区间的结果做 1 次运算即可求出原区间的结果。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, x, y) for (int i = (x); i <= (y); i++)
#define per(i, x, y) for (int i = (x) ;i >= (y); i--)
#define ll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
constexpr int N = 1e5 + 7;
constexpr int P = 998244353;
int n, m, a[N], f[20][N];
void solve() {
cin >> n >> m;
rep (i, 1, n) cin >> a[i];
rep (i, 1, n) f[0][i] = a[i];
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
f[j][i] = max(f[j - 1][i], f[j - 1][i + (1 << (j - 1))]);
}
}
while (m--) {
int l, r;
cin >> l >> r;
int j = __lg(r - l + 1);
cout << max(f[j][l], f[j][r - (1 << j) + 1]) << '\n';
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int oT_To = 1;
// cin >> oT_To;
while (oT_To--) solve();
return 0;
}
警示后人,如果你用的 cin,请关闭同步流!!!
例题
B - 查单词
就是 ST 表变成字符串,再手写 MIN 比较即可!
#include <bits/stdc++.h>
using namespace std;
#define rep(i, x, y) for (int i = (x); i <= (y); i++)
#define per(i, x, y) for (int i = (x) ;i >= (y); i--)
#define ll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
constexpr int N = 1e5 + 7;
constexpr int P = 998244353;
int n, m;
string s[N], f[20][N];
string MIN(string a, string b) {
string xx = a, yy = b;
for (auto &x : xx) if (x >= 'a' && x <= 'z') x -= 32;
for (auto &x : yy) if (x >= 'a' && x <= 'z') x -= 32;
if (xx > yy) return a;
else return b;
}
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> s[i];
f[0][i] = s[i];
}
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
f[j][i] = MIN(f[j - 1][i], f[j - 1][i + (1 << (j - 1))]);
}
}
while (m--) {
int l, r;
cin >> l >> r;
int j = __lg(r - l + 1);
cout << MIN(f[j][l], f[j][r - (1 << j) + 1]) << '\n';
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int oT_To = 1;
// cin >> oT_To;
while (oT_To--) solve();
return 0;
}
C - Bad Hair Day S
可以发现,如果右端点越大,就越难满足条件,所以可以二分答案。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, x, y) for (int i = (x); i <= (y); i++)
#define per(i, x, y) for (int i = (x) ;i >= (y); i--)
#define ll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
constexpr int N = 1e5 + 7;
constexpr int P = 998244353;
int n, m, a[N], f[20][N];
int calc(int l, int r) {
int j = __lg(r - l + 1);
return max(f[j][l], f[j][r - (1 << j) + 1]);
}
bool check(int l,int r){
int res = calc(l, r);
return res < a[l - 1];
}
void solve() {
cin >> n;
rep (i, 1, n) cin >> a[i];
rep (i, 1, n) f[0][i] = a[i];
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
f[j][i] = max(f[j - 1][i], f[j - 1][i + (1 << (j - 1))]);
}
}
ll ans = 0;
for (int i = 1; i <= n; i++) {
int l = i + 1, r = n;
while (l <= r) {
int mid = (l + r) / 2;
if(check(i + 1, mid))
l = mid + 1;
else
r = mid - 1;
}
ans += (r - i);
}
cout << ans << endl;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int oT_To = 1;
// cin >> oT_To;
while (oT_To--) solve();
return 0;
}
这里的二分其实是错的,详见彩蛋!
D - gcd 区间
RMQ 问题改一改就好了。
注意时间复杂度!
#include <bits/stdc++.h>
using namespace std;
#define rep(i, x, y) for (int i = (x); i <= (y); i++)
#define per(i, x, y) for (int i = (x) ;i >= (y); i--)
#define ll long long
#define gcd __gcd
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
constexpr int N = 1e5 + 7;
constexpr int P = 998244353;
int n, m, a[N], f[20][N];
void solve() {
cin >> n >> m;
rep (i, 1, n) cin >> a[i];
rep (i, 1, n) f[0][i] = a[i];
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
f[j][i] = gcd(f[j - 1][i], f[j - 1][i + (1 << (j - 1))]);
}
}
while (m--) {
int l, r;
cin >> l >> r;
int j = __lg(r - l + 1);
cout << gcd(f[j][l], f[j][r - (1 << j) + 1]) << '\n';
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int oT_To = 1;
// cin >> oT_To;
while (oT_To--) solve();
return 0;
}
E - Sound 静音问题
这道题好毒瘤!
本来是单调队列的题目,非要用 st 表,怎么办,会报空间。
还能怎么办,m 是定长,所以指数开到 \(\log_2 m\) 就可以,或者用一个数组存下来,这样子也可以离线做!
#include <bits/stdc++.h>
using namespace std;
#define rep(i, x, y) for (int i = (x); i <= (y); i++)
#define per(i, x, y) for (int i = (x) ;i >= (y); i--)
#define ll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
constexpr int N = 1e6 + 7;
constexpr int P = 998244353;
int n, m, c, a[N];
vector <vector <int>> f;
int calc(int l, int r) {
int j = __lg(r - l + 1);
return max(f[j][l], f[j][r - (1 << j) + 1]);
}
int calc1(int l, int r) {
int j = __lg(r - l + 1);
return min(f[j][l], f[j][r - (1 << j) + 1]);
}
void solve() {
cin >> n >> m >> c;
int k = __lg(n) + 1;
f.resize(k);
for (int i = 0; i < k; i++) f[i].resize(n + 1);
rep (i, 1, n) {
cin >> a[i];
f[0][i] = a[i];
}
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
f[j][i] = max(f[j - 1][i], f[j - 1][i + (1 << (j - 1))]);
}
}
int cnt = 0;
vector <int> ve;
for (int i = 1; i + m - 1 <= n; i++) {
ve.pb(calc(i, i + m - 1));
}
rep (i, 1, n) {
f[0][i] = a[i];
}
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
f[j][i] = min(f[j - 1][i], f[j - 1][i + (1 << (j - 1))]);
}
}
for (int i = 0; i < (int)ve.size(); i++) {
if (ve[i] - calc1(i + 1, i + m) <= c) {
cnt++;
cout << i + 1 << endl;
}
}
if (!cnt) cout << "NONE" << endl;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int oT_To = 1;
// cin >> oT_To;
while (oT_To--) solve();
return 0;
}
G - Max Min
本质和 C 没有任何区别,就是最后的答案求一个交集。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, x, y) for (int i = (x); i <= (y); i++)
#define per(i, x, y) for (int i = (x) ;i >= (y); i--)
#define lll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
constexpr int N = 2e5 + 7;
constexpr int P = 998244353;
int n, x, y, a[N], f[20][N], dp[20][N];
int calc(int l, int r) {
int j = __lg(r - l + 1);
return max(f[j][l], f[j][r - (1 << j) + 1]);
}
int calc1(int l, int r) {
int j = __lg(r - l + 1);
return min(dp[j][l], dp[j][r - (1 << j) + 1]);
}
void solve() {
cin >> n >> x >> y;
rep (i, 1, n) cin >> a[i];
rep (i, 1, n) f[0][i] = a[i], dp[0][i] = a[i];
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
f[j][i] = max(f[j - 1][i], f[j - 1][i + (1 << (j - 1))]);
}
}
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
dp[j][i] = min(dp[j - 1][i], dp[j - 1][i + (1 << (j - 1))]);
}
}
long long ans = 0;
for (int i = 1; i <= n; i++) {
if (calc(i, n) < x || calc1(i, n) > y) continue;
int L = i, R = n, l = i, r = n, LL = i, RR = n, ll = i, rr = n;
while (L < R) {
int mid = (L + R) / 2;
if (calc(i, mid) >= x) R = mid; // a[r] >= x;
else L = mid + 1;
}
if (calc(i, n) <= x) r = n;
else {
while (l < r) {
int mid = (l + r) / 2;
if (calc(i, mid) > x) r = mid; // a[r] > x
else l = mid + 1;
}
r--;
}
while (LL < RR) {
int mid = (LL + RR) / 2;
if (calc1(i, mid) <= y)
RR = mid;
else
LL = mid + 1;
}
if (calc1(i, n) >= y) rr = n;
else {
while (ll < rr) {
int mid = (ll + rr) / 2;
if (calc1(i, mid) < y) rr = mid;
else ll = mid + 1;
}
rr--;
}
// printf("%d %d %d %d\n", L, R, l, r);
// printf("%d %d %d %d\n", LL, RR, ll, rr);
ans += max(0LL, 1LL * min(r, rr) - max(R, RR) + 1);
}
// if (x == y) ans += n;
cout << ans << endl;
}
int main() {
// ios::sync_with_stdio(0);
// cin.tie(0), cout.tie(0);
int oT_To = 1;
// cin >> oT_To;
while (oT_To--) solve();
return 0;
}
彩蛋(惨淡)
昨天上课,Jol3r 老师帮我看了我的二分,发现错完了!
int L = 0, R = n + 1;
while (L + 1 < R) {
int mid = (L + R) / 2;
if (check(mid))
R = mid;
else
L = mid;
}
这个代码求二分查找没有任何问题,但是二分答案时可能会错过答案。
其实我 C 题代码的二分也是错的,只不过碰巧对了。
如果是二分答案,建议写
int L = 1, R = MAXN;
while (L < R) {
int mid = (L + R) / 2;
if (check(i, mid)) R = mid;
else L = mid + 1;
}
这里 L 一定是答案的上界。
不然可能会错过答案。
不过问一下,这个二分是什么类型的?(左闭右闭?)

浙公网安备 33010602011771号