P3865 【模板】ST 表 && RMQ 问题题解
UPD:更正时间复杂度。
UPD:修改了对于预处理中公式的证明。
UPD:更换了更美观的图片。
前置知识
- 倍增。
RMQ 问题
在一个数列中,回答若干询问,返回指定区间内的最小值或最大值。
st 表简介
以下以求最大值为例。
作用
st 表是一种能以
O(n\log n) 复杂度预处理,O(1) 查询的数据结构。一般不支持修改操作。通常用于解决 RMQ 问题。原理
st 算法通过预处理以点
i 为左边界,区间长为2^k 的区间内的最大值,记为st_{i,k} ,然后在查询时快速得出结果。
如下图所示为左边界为
而假设我们当前要求区间
如下图为查询区间
正确性证明
首先证明
然后显然这样算一定会覆盖区间
实现
预处理
可以从后往前,用类似 dp 的方式预处理。转移方程显然为:
两重循环即可。
同时由于 c++ 中 log2(x) 函数的复杂度为
特别地,
证明
不会证明被审核员打回并嘲讽了。所以来简单证明一下。
求
代码
void init() {
lg[0] = -1;
for (int i = 1; i < N; i++)
lg[i] = lg[i / 2] + 1;
for (int i = n; i >= 1; i--) {
for (int j = 0; i + (1 << j) -1 <= n; j++) {
if (j == 0)
st[i][j] = a[i];
else
st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
}
}
查询
首先计算得出
代码
int query(int a, int b) {
int len = (b - a + 1);
int k = lg[len];
return max(st[a][k], st[b - (1ll << k) +1][k]);
}
复杂度分析
预处理时间复杂度
空间复杂度显然
完整代码
本题时限较紧,需要用快读卡常。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define dd double
inline ll read() {
ll x = 0, f = 1;
char ch;
while (((ch = getchar()) < 48 || ch > 57) && ch != EOF)if (ch == '-')f = -1;
if (ch == EOF)x = EOF;
while (ch >= 48 && ch <= 57)x = x * 10 + ch - 48, ch = getchar();
return x * f;
}
const ll N = 1e5+9, logN = 30;
ll st[N][logN];
ll n, m;
ll lg[N];
ll a[N];
void init() {
lg[0] = -1;
for (int i = 1; i < N; i++)
lg[i] = lg[i / 2] + 1;
for (int i = n; i >= 1; i--) {
for (int j = 0; i + (1 << j) -1 <= n; j++) {
if (j == 0)
st[i][j] = a[i];
else
st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
}
}
int query(int a, int b) {
int len = (b - a + 1);
int k = lg[len];
return max(st[a][k], st[b - (1ll << k) +1][k]);
}
int main() {
n = read();
m = read();
for (int i = 1; i <= n; i++)
a[i] = read();
init();
for (int i = 1; i <= m; i++) {
ll l = read(), r = read();
cout << query(l, r);
putchar('\n');
}
return 0;
}