RMQ(Range Minimun/Maximum Query),即区间查询最值,适用于需要多次查询区间最值的问题。RMQ需要 \(O(n\log n)\) 的预处理,之后可以在 \(O(1)\) 的时间内处理每次查询。
下面的演示我们以查询最小值为例。
RMQ算法采用倍增的思想,设 \(st[i][j]\) 表示区间 \([i, i + 2 ^ j - 1]\) 中的最值,即 \(i\) 后 \(2^j\) 个数字的最值。我们可以把区间分为两半 \([i,i+2^{j-1}-1]\) 和 \([i+2^{j-1},i+2^j-1]\),则最值可以表示为这两个子区间的最值,显然这两个区间最值为 \(st[i][j-1]\) 和 \(st[i+(1<<j-1))][j-1]\) 。则状态转移方程为:
\[st[i][j] = min(st[i][j-1], st[i+2^{j-1}][j-1]) \]我们把这个数组记为\(st\)表,于是我们得出代码:
Code
void RMQ () { for (int i = 1; i <= n; i++) st[i][0] = a[i]; // 边界处理 for (int j = 1; (1 << j) <= n; j++) for (int i = 1; i + (1 << j) - 1 <= n; i++) st[i][j] = min(st[i][j-1], st[i+(1<<j-1)][j-1]); }
注意,状态转移方程是从小区间转移到大区间,所以我们要先处理小区间。也就是范围要先循环。
由于\(st\)表是由倍增构建的,所以只能存储2的幂次方长度的区间最值,那么我们要怎么求任意区间的最值呢?
同样的,我们把区间分为两段,\([l,l+2^k-1]\) 和 \([r-2^k+1,r]\),这里k表示长度不超过\(r-l+1(查询区间的长度)\)的倍增区间的倍增幂指数。如下图:
显然,查询的区间最值只需要在这两个子区间中求出最值即可,即:
\[Minimum = min(st[i][k], st[r-2^k+1][r]); \qquad k = log2(r-l+1) \]Code
int query (int l, int r) { int k = log2(r-l+1); return min(st[i][k], st[r-(1<<k)+1][k]); }