RMQ

RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:对于长度为n的数列A,回答若干次询问RMQ(i,j),返回数列A中下标在区间[i,j]中的最小/大值。

本文介绍一种比较高效的ST算法解决这个问题。ST(Sparse Table)算法可以在O(nlogn)时间内进行预处理,然后在O(1)时间内回答每个查询。



1)预处理

设A[i]是要求区间最值的数列,F[i, j]表示从第i个数起连续2^j个数中的最大值。(DP的状态)

例如:

A数列为:3 2 4 5 6 8 1 2 9 7

F[1,0]表示第1个数起,长度为2^0=1的最大值,其实就是3这个数。同理 F[1,1] = max(3,2) = 3, F[1,2]=max(3,2,4,5) = 5,F[1,3] = max(3,2,4,5,6,8,1,2) = 8;

并且我们可以容易的看出F[i,0]就等于A[i]。(DP的初始值)


我们把F[i,j]平均分成两段(因为F[i,j]一定是偶数个数字),从 i 到i + 2 ^ (j - 1) - 1为一段,i + 2 ^ (j - 1)到i + 2 ^ j - 1为一段(长度都为2 ^ (j - 1))。于是我们得到了状态转移方程F[i, j]=max(F[i,j-1], F[i + 2^(j-1),j-1])。


2)查询

假如我们需要查询的区间为(i,j),那么我们需要找到覆盖这个闭区间(左边界取i,右边界取j)的最小幂(可以重复,比如查询1,2,3,4,5,我们可以查询1234和2345)。

因为这个区间的长度为j - i + 1,所以我们可以取k=log2( j - i + 1),则有:RMQ(i, j)=max{F[i , k], F[ j - 2 ^ k + 1, k]}。

举例说明,要求区间[1,5]的最大值,k = log2(5 - 1 + 1)= 2,即求max(F[1, 2],F[5 - 2 ^ 2 + 1, 2])=max(F[1, 2],F[2, 2]);



void ST(int n) {
    for (int i = 1; i <= n; i++)
        dp[i][0] = A[i];
    for (int j = 1; (1 << j) <= n; j++) {
        for (int i = 1; i + (1 << j) - 1 <= n; i++) {
            dp[i][j] = max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
        }
    }
}
int RMQ(int l, int r) {
    int k = 0;
    while ((1 << (k + 1)) <= r - l + 1) k++;
    return max(dp[l][k], dp[r - (1 << k) + 1][k]);
}


### 单调栈实现RMQ算法的原理与代码示例 单调栈是一种特殊的数据结构,其内部元素按照一定的顺序排列(例如单调递增或单调递减)。在解决 RMQ(Range Minimum/Maximum Query)问题时,单调栈可以用来快速找到区间内的最小值或最大值[^1]。 #### 原理 单调栈的核心思想是通过维护一个具有单调性质的栈来减少不必要的比较操作。对于 RMQ 问题,单调栈可以通过以下方式实现: - **构造影响力区间**:使用单调栈计算每个元素的影响力区间,即该元素作为区间最小值或最大值所能覆盖的范围。 - **预处理**:通过一次遍历数组,利用单调栈记录每个元素的左边界和右边界,从而确定其影响力区间。 - **查询优化**:在预处理完成后,RMQ 查询可以在 O(1) 时间内完成,因为每个区间的最小值或最大值已经被提前计算并存储。 #### 代码示例 以下是使用单调栈实现 RMQ 的 Python 示例代码: ```python def preprocess_min(arr): n = len(arr) left = [-1] * n # 记录每个元素左侧最近的小于它的元素位置 right = [n] * n # 记录每个元素右侧最近的小于它的元素位置 stack = [] # 单调递增栈 # 计算左侧边界 for i in range(n): while stack and arr[stack[-1]] >= arr[i]: stack.pop() if stack: left[i] = stack[-1] stack.append(i) stack.clear() # 清空栈以便复用 # 计算右侧边界 for i in range(n - 1, -1, -1): while stack and arr[stack[-1]] >= arr[i]: stack.pop() if stack: right[i] = stack[-1] stack.append(i) return left, right def rmq_query(arr, queries): left, right = preprocess_min(arr) result = [] for l, r in queries: min_val = float('inf') for i in range(l, r + 1): if left[i] < l and right[i] > r: # 检查是否为区间最小值 min_val = min(min_val, arr[i]) result.append(min_val) return result # 测试代码 arr = [3, 1, 2, 1, 4] queries = [(0, 4), (1, 3), (2, 4)] print(rmq_query(arr, queries)) # 输出区间最小值 ``` #### 解释 1. `preprocess_min` 函数通过两次遍历数组构建单调栈,分别计算每个元素的左侧和右侧边界。 2. `rmq_query` 函数根据预处理结果回答多个 RMQ 查询。对于每个查询区间 `(l, r)`,检查该区间内的所有元素,判断其是否为最小值,并返回结果。 #### 性能分析 - **时间复杂度**:预处理阶段的时间复杂度为 O(n),查询阶段的时间复杂度为 O(q * k),其中 q 是查询次数,k 是平均每个查询涉及的元素数量。 - **空间复杂度**:O(n),用于存储左侧和右侧边界信息。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值