ST 表模板
原理
ST 表用于静态区间最值,预处理 \(O(n\log n)\),单次查询 \(O(1)\),不支持修改。 \(fij\):从第 i 个数开始,长度为 \(2^j\) 的区间最大值。
预处理转移
(fij = max(fij-1, fi + 2\^{j-1}j-1))
查询
区间 (l,r),长度 (len=r-l+1),k=⌊log2(len)⌋
ans=max(flk, fr−2k+1k)
完整 AC 代码
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAXN = 1e5 + 10;
const int LOG = 20; // 2^20 > 1e6 够用
int n, m;
int a[MAXN];
int st[MAXN][LOG];
int log2_[MAXN]; // 预处理log2,查询更快
// 预处理log数组
void preLog() {
log2_[1] = 0;
for (int i = 2; i < MAXN; ++i)
log2_[i] = log2_[i / 2] + 1;
}
// 构建ST表
void build() {
// 长度2^0=1
for (int i = 1; i <= n; ++i)
st[i][0] = a[i];
// 倍增
for (int j = 1; j < LOG; ++j)
for (int i = 1; i + (1 << j) - 1 <= n; ++i)
st[i][j] = max(st[i][j-1], st[i + (1 << (j-1))][j-1]);
}
// 查询[l, r]最大值
int query(int l, int r) {
int k = log2_[r - l + 1];
return max(st[l][k], st[r - (1 << k) + 1][k]);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
preLog();
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> a[i];
build();
while (m--) {
int l, r;
cin >> l >> r;
cout << query(l, r) << '\n';
}
return 0;
}
二维 ST 表模板
一、适用场景
静态二维矩阵,多次询问任意子矩形最大值 / 最小值,无单点修改
- 预处理:\(O(nm\log n \log m)\)
- 单次查询:\(O(1)\)
- 限制:矩阵长宽不超过 250/1000 都可,LOG 开到 10 足够(\(2^{10}=1024\))
定义
\(stmaxijkl\):左上角\((i,j)\),高\(2^k\)、宽\(2^l\)的矩形最大值 \(stminijkl\):同上,存最小值
查询原理
矩形 x1,y1左上,x2,y2 右下
kx=log2(x2−x1+1), ky=log2(y2−y1+1)
用四块重叠区间覆盖整个矩形,取最大 / 最小:
max=max(
stmaxx1y1kxky,
stmaxx2−2kx+1y1kxky,
stmaxx1y2−2ky+1kxky,
stmaxx2−2kx+1y2−2ky+1kxky
)
最小值同理用 min
二、预处理 log 数组
提前预处理 1~maxn 的 log2 下取整,避免浮点运算,加速查询。
三、可直接复制完整模板(1 下标,通用)
#include <iostream>
#include <algorithm>
using namespace std;
// 按需修改:矩阵最大长宽、倍增层数
const int MAXN = 255;
const int LOG = 10;
int n, m; // n行 m列
int a[MAXN][MAXN]; // 原矩阵
int stmax[MAXN][MAXN][LOG][LOG];
int stmin[MAXN][MAXN][LOG][LOG];
int lg[MAXN]; // lg[i] = floor(log2(i))
// 预处理log数组
void pre_log() {
lg[1] = 0;
for (int i = 2; i < MAXN; i++)
lg[i] = lg[i / 2] + 1;
}
// 构建二维ST表
void build() {
// 初始化 2^0 * 2^0 单点
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
stmax[i][j][0][0] = stmin[i][j][0][0] = a[i][j];
// 第一阶段:行倍增,高度固定 2^0,加宽
for (int l = 1; l < LOG; l++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j + (1 << l) - 1 <= m; j++) {
stmax[i][j][0][l] = max(stmax[i][j][0][l-1], stmax[i][j + (1 << (l-1))][0][l-1]);
stmin[i][j][0][l] = min(stmin[i][j][0][l-1], stmin[i][j + (1 << (l-1))][0][l-1]);
}
}
}
// 第二阶段:列倍增,宽度所有l,加高
for (int k = 1; k < LOG; k++) {
for (int i = 1; i + (1 << k) - 1 <= n; i++) {
for (int j = 1; j <= m; j++) {
for (int l = 0; l < LOG; l++) {
stmax[i][j][k][l] = max(stmax[i][j][k-1][l], stmax[i + (1 << (k-1))][j][k-1][l]);
stmin[i][j][k][l] = min(stmin[i][j][k-1][l], stmin[i + (1 << (k-1))][j][k-1][l]);
}
}
}
}
}
// 查询矩形 [x1,y1] ~ [x2,y2] 最大值
int get_max(int x1, int y1, int x2, int y2) {
int kx = lg[x2 - x1 + 1];
int ky = lg[y2 - y1 + 1];
int lenx = 1 << kx;
int leny = 1 << ky;
return max({
stmax[x1][y1][kx][ky],
stmax[x2 - lenx + 1][y1][kx][ky],
stmax[x1][y2 - leny + 1][kx][ky],
stmax[x2 - lenx + 1][y2 - leny + 1][kx][ky]
});
}
// 查询矩形 [x1,y1] ~ [x2,y2] 最小值
int get_min(int x1, int y1, int x2, int y2) {
int kx = lg[x2 - x1 + 1];
int ky = lg[y2 - y1 + 1];
int lenx = 1 << kx;
int leny = 1 << ky;
return min({
stmin[x1][y1][kx][ky],
stmin[x2 - lenx + 1][y1][kx][ky],
stmin[x1][y2 - leny + 1][kx][ky],
stmin[x2 - lenx + 1][y2 - leny + 1][kx][ky]
});
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
pre_log();
// 读入矩阵行列
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j];
build();
int q;
cin >> q;
while (q--) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
int mx = get_max(x1, y1, x2, y2);
int mi = get_min(x1, y1, x2, y2);
cout << mx - mi << '\n';
}
return 0;
}