查询区间最值
算法简介
RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。
——百度百科
ST(Sparse Table,稀疏表)算法是求解RMQ问题的经典在线算法,以O(nlogn)时间预处理,然后在O(1)时间内回答每个查询。
算法流程
ST算法实际上采用了DP的思想
初始化
定义 表示区间 内的最小值, 为原序列
那么显然
MATHJAX-SSR-1691
状态转移方程?
首先,任意一个区间的最小值等于 这个区间前一半的最小值 这个区间后一半点最小值
这个很好理解吧
把结合进去,就是
还有什么细节?
上面的式子看的你很想递归是吧(反正我是)
如果你不想递归的话,你八成会:
1 2 3
| for (int i = 1; ...) for (int j = 1; ...) F[i][j] = ...
|
其实……这样都是错的,这样会导致有几个状态被过早地枚举
我们要把枚举 的循环放在外层,至于为什么……你模拟一下就行了
查询
此处的内容可能有点难以理解,请消化不了的同学多看几遍
上面说了查询是 的听起来就好简单啊
实现确实是很简单,但是原理就……也是很简单
首先给你一个定理:
对于任意 ,都有
然后令查询区间的长度
那么根据上边可得
这意味着什么?
这意味着查询的区间有重叠!
不过这并不能意味啥,重叠又怎么样,只是查询的区间变了
1 2 3 4 5 6 7 8 9 10 11
| 原来我们查询区间,都是查询这个区间的一半 比如更新[l,r]之间的最小值就是 f[l][r] = std::min(f[l][mid], f[mid + 1][r]);
但是这次不一样,这次的mid超过了区间的一半 那就可以这么写:
int Query(int l, int r) { int k = std::log(r - (l - 1)) / std::log(2); return std::min(f[l][k], f[r - ((1 << k) -1)][k]); }
|
是 的吧
它查询的区间相当于是这样的:
(画的不准确,仅供参考)
1 2 3 4 5
| _ _ _ _ _ _ _ _ _ -------==== f[l][k] ====------ f[r - ((1 << k) - 1)][k] 等号就是两个查询区间的并集
|
这也就是它为什么不能查询区间和的原因
前缀和足够了
qwq
伪代码
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <iostream> #include <cstring> #include <cstdio> #include <cmath>
#define FILE_IN(__fname) freopen(__fname, "r", stdin) #define FILE_OUT(__fname) freopen(__fname, "w", stdout) #define IMPROVE_IO() std::ios::sync_with_stdio(false)
using std::cin; using std::cout; using std::endl;
const int MAXN = (100000 + 10) * 2; const int MAXLOG = 17 + 10;
int n, q; int Table[MAXN][MAXLOG];
void BuildTable() { for (int j = 1; (1 << j) <= n; ++j) { for (int i = 1; i + (1 << j) - 1 <= n; ++i) { Table[i][j] = std::max(Table[i][j-1], Table[i + (1 << (j - 1))][j - 1]); } } }
int Query(int l, int r) { int k = std::log(r - (l - 1)) / std::log(2); return std::max(Table[l][k], Table[r - ((1 << k) - 1)][k]); }
int main() { scanf("%d %d", &n, &q); for (int i = 1; i <= n; ++i) scanf("%d", &Table[i][0]); BuildTable(); for (int i = 1; i <= q; ++i) { int l = 0, r = 0; scanf("%d %d", &l, &r); printf("%d\n", Query(l, r)); } return 0; }
|
例题
洛谷P3865【模板】ST表