RMQ 算法
简介 #
RMQ 是 Range Maximum/Minimum Query 的缩写,意为区间的最大(或最小)值.
问题 #
已知数组 $A$ 中一共有 $n$ 个元素,给出 $m$ 次询问:
- 给出 $l,r$,求 $A[l\cdots r]$ 中的最大值.
int n, a[];
void query(int l, int r) { // 暴力算法
int ans = a[l];
for(int i = l; i <= r; i ++)
ans = max(ans, a[i]);
return ans;
}
暴力算法($\color{red}{×}$) | RMQ 算法($\color{green}{\sqrt{}}$) |
|
---|---|---|
预处理 | $\color{green}{O(0)}$ | $\color{green}{O(n\log{n})}$ |
单次查询 | $\color{green}{O(n)}$ | $\color{green}{O(1)}$ |
$m$ 次询问 | $\color{red}{O(mn)}$ | $\color{green}{O(m)}$ |
预处理 #
$f[i,j]$ 表示从 $A[i]$ 开始往后数 $2^j$ 个数的最大值,也就是 $\max\{A[i]\sim A[i+2^j-1]\}$.将 $2^j$ 个数从中间平均分成两部分,每一部分的元素为 $2^{j-1}$ 个.
$$\overbrace{\underbrace{A[i],A[i+1],\cdots,A[i+2^{j-1}-1]}_{{ 2^{j-1}}个元素},\underbrace{A[i+2^{j-1}],\cdots,A[i+2^j-1]}_{{ 2^{j-1}}个元素}}^{{ 2^j}个元素}$$
整个区间的最大值一定为左右两部分的最大值:
$$ f[i,j]=\max(f[i,j-1],f[i+2^{j-1},j-1]) $$
由于 $f[*,j]$ 是由 $f[*,j-1]$ 推出的,故第一层循环枚举 $j=0→\log{n}$.
初始条件 | 计算顺序 | 时间复杂度 |
---|---|---|
$f[i,0]=A[i]$ | $f[1→n,0→\log{n}]$ | $O(n\log{n})$ |
const int N = 1e6 + 1, logN = 32;
int n, a[N], f[N][logN];
void pre() {
for(int i = 1; i <= n; i ++)
f[i][0] = a[i];
for(int j = 1; j < logN; j ++) // f[][] 的第 2 维一定要先枚举
for(int i = 1; i + (1 << (j - 1)) <= n; i ++)
f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1)))][j - 1]);
}
原理 #
现在我们要查询 $A[l]\sim A[r]$ 的最大值.$A[l]\sim A[r]$ 一共有 $r-l+1$ 个元素,我们设 $s=\log(r-l+1)$,然后在整个区间内划出两个长度为 $2^s$ 的子区间:
根据前面的定义,左子区间的最大值为 $f[l,s]$,右子区间的最大值为 $f[r-2^s+1,s]$.
虽然两个子区间有重叠部分,但它们包含了整个 $[l,r]$ 区间.因此
$$ \max\{A[l]\sim A[r]\}=\max(f[l,s],f[r-2^s+1,s]) $$
由于 $C++$ 提供的 $log2()$ 函数太慢,于是预处理 $Log[ \ ]$ 数组替代 $log2()$ 函数.
int Log[N]; // Log[i] = log2(i)
int pre() {
...
Log[0] = -1; // 为了使 Log[1] = Log[1/2] + 1 = 0, Log[0] 得先赋值 -1
for(int i = 1; i <= n; i ++)
Log[i] = Log[i / 2] + 1;
}
int query(int l, int r) { // 返回 a[l] ~ a[r] 的最大值
int s = Log[r - l + 1];
return max(f[l][s], f[r - (1 << s) + 1][s]);
}
模板 #
const int N = 1e6, logN = 32;
int n, l, r, a[N], f[N][logN], Log[N];
void pre() {
Log[0] = -1; // Log[] 的预处理在这里
for(int i = 1; i <= n; i ++)
f[i][0] = a[i], Log[i] = Log[i / 2] + 1;
for(int j = 1; j < logN; j ++)
for(int i = 1; i + (1 << (j - 1)) <= n; i ++)
f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
int query(int l, int r) {
int s = Log[r - l + 1];
return max(f[l][s], f[r - (1 << s) + 1][s]);
}
int main() {
cin >> n;
for(int i = 1; i <= n; i ++)
cin >> a[i];
pre();
while(cin >> l >> r)
cout << query(l, r) << endl;
}