RMQ-简单分析与代码实现

RMQ

Range Minimum/Maximum Query

  • 作用:快速求区间的最大/最小值
  • 本质是一个动态规划

思考流程

  • 假设我们有序列 w [ 1 ∼ N ] w[1 \sim N] w[1N]

  • 以最大值为例

  • 定义状态 f ( i , j ) f(i,j) f(i,j):代表从索引 i i i开始,长度是 2 j 2^j 2j的区间的最大值

  • 也就是说, f ( i , j ) f(i,j) f(i,j)代表的就是区间 [ i , i + 2 j − 1 ] [i, i + 2 ^ j - 1] [i,i+2j1]区间内的最大值

那么, f ( i , j ) f(i,j) f(i,j)该如何求解呢?

  1. 首先,我们可以发现当 j = 0 j=0 j=0时, f ( i , 0 ) = w [ i ] f(i,0)=w[i] f(i,0)=w[i],即当前代表的区间就是它自己,因此它自身就是最大值;

  2. j ≠ 0 j \neq 0 j=0时,我们可以发现,

    • 我们可以将区间 [ i , i + 2 j − 1 ] [i, i + 2 ^j - 1] [i,i+2j1]拆分为 [ i , i + 2 j − 1 − 1 ] , [ i + 2 j − 1 , i + 2 j − 1 ] [i, i + 2 ^{j - 1} - 1], [i +2^{j-1}, i + 2^j - 1] [i,i+2j11],[i+2j1,i+2j1]

    • 分别对应了 f ( i , j − 1 ) f(i, j-1) f(i,j1) f ( i + 2 j − 1 , j − 1 ) f(i + 2^{j - 1}, j - 1) f(i+2j1,j1)

按照 f ( i , j ) f(i, j) f(i,j)代表的含义,我们可以发现 f ( i , j ) = m a x { f ( i , j − 1 ) , f ( i + 2 j − 1 , j − 1 ) } f(i,j) = max\{f(i, j - 1), f(i + 2^{j - 1}, j - 1) \} f(i,j)=max{f(i,j1),f(i+2j1,j1)}

至此,我们就可以从 j = 0 j=0 j=0开始向上循环预处理 f ( i , j ) f(i,j) f(i,j)

初始化完成后,对于给定的区间 l , r l, r l,r,该如何求 [ l , r ] [l, r] [l,r]范围内的最大值呢?

我们令区间长度 l e n = r − l + 1 len = r - l + 1 len=rl+1

  • 我们可以找到一个 k k k,满足性质 2 k ≤ l e n , 2 ⋅ 2 k ≥ l e n 2^k \le len, \quad 2 \cdot 2^k \ge len 2klen,22klen
  • 满足这个性质的 k k k,我们可以通过计算得到 k = ⌊ log ⁡ 2 l e n ⌋ k = \lfloor \log_2{len}\rfloor k=log2len

这样,我们可以将区间 [ l , r ] [l, r] [l,r]分为 [ l , l + 2 k − 1 ] [l, l + 2^k - 1] [l,l+2k1] [ r − 2 k + 1 , r ] [r - 2^k + 1, r] [r2k+1,r]

分别对应 f ( l , k ) f(l, k) f(l,k) f ( r − 2 k + 1 , k ) f(r - 2^k + 1, k) f(r2k+1,k)

  • 注意,这两个区间不是正好构成 [ l , r ] [l,r] [l,r],它们之间是有重叠部分的
  • 但对于最大最小值,重复计算或者多次对比,是不会影响最终结果的

我们用一个实际的例子来看一下

  • 打个比方, w = { 1 , 3 , 2 , 7 , 4 , 1 } w = \{1, 3, 2, 7, 4, 1\} w={1,3,2,7,4,1},下标从 1 1 1开始

    • l = 1 , r = 6 , k = 2 l = 1, r = 6, k = 2 l=1,r=6,k=2
    • 根据上面提到的分法,将区间 [ 1 , 6 ] [1,6] [1,6]转化为 [ 1 , 4 ] [1, 4] [1,4] [ 3 , 6 ] [3, 6] [3,6],分别对应 [ l , l + 2 k − 1 ] [l, l + 2^k - 1] [l,l+2k1] [ r − 2 k + 1 , r ] [r - 2^k + 1, r] [r2k+1,r]
      • [ l , l + 2 k − 1 ] → f ( l , k ) → f ( 1 , 2 ) → [ 1 , 1 + 2 2 − 1 ] → [ 1 , 4 ] [l, l + 2^k - 1] \rightarrow f(l, k) \rightarrow f(1,2) \rightarrow [1, 1 + 2^2 -1] \rightarrow [1, 4] [l,l+2k1]f(l,k)f(1,2)[1,1+221][1,4]
      • [ r − 2 k + 1 , r ] → f ( r − 2 k + 1 , k ) → f ( 3 , 2 ) → [ 6 − 2 2 + 1 , 6 ] → [ 3 , 6 ] [r - 2^k + 1, r] \rightarrow f(r - 2^k + 1, k) \rightarrow f(3,2) \rightarrow [6 - 2^2 + 1, 6] \rightarrow [3, 6] [r2k+1,r]f(r2k+1,k)f(3,2)[622+1,6][3,6]
  • 所以,可得区间内最大值即为: m a x { f ( 1 , 2 ) , f ( 3 , 2 ) } = 7 max \{ f(1, 2), f(3, 2) \} = 7 max{f(1,2),f(3,2)}=7

至此,我们将初始化和最大值查询结合,就是RMQ的完整写法了

时间复杂度:

  • 初始化: O ( N ⋅ log ⁡ N ) O(N \cdot \log N) O(NlogN)
  • 查询:接近 O ( 1 ) O(1) O(1)

最后还有一道例题

  • 之前的题目可能有朋友打不开链接,换了一道
  • 但在这题中,用Java跑的话会MLE(Memory Limit Exceeded),没有想到好的解决办法
    • 算法运行时间方面是没问题的
    • 应该是输入输出流上面占多了空间
  • 同样的写法C++可正常通过,因此将对应代码一起贴在这

题目链接:P3865 【模板】ST 表 - 洛谷

Java写法

import java.io.*;

public class Main {
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    static int N = 100010, M = 20; // M = logN + C, C任意
    static int n, m;
    static int[] w = new int[N];
    static int[][] f = new int[N][M];

    static int Int(String s) {
        return Integer.parseInt(s);
    }

    static void init() {
        for (int j = 0; j < M; j ++) {
            for (int i = 1; i + (1 << j) - 1 <= n; i ++) {
                if (j == 0) {
                    f[i][j] = w[i];
                } else {
                    f[i][j] = Math.max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
                }
            }
        }
    }

    static int query(int l, int r) {
        int len = r - l + 1;
        int k = (int) (Math.log(len) / Math.log(2));
        return Math.max(f[l][k], f[r - (1 << k) + 1][k]);
    }

    public static void main(String[] args) throws IOException {
        String[] arr = in.readLine().split(" ");
        n = Int(arr[0]); m = Int(arr[1]);

        arr = in.readLine().split(" ");
        for (int i = 1; i <= n; i ++) {
            w[i] = Int(arr[i - 1]);
        }

        init();

        while (m -- > 0) {
            arr = in.readLine().split(" ");
            int l = Int(arr[0]), r = Int(arr[1]);
            out.write(query(l, r) + "\n");
        }

        out.flush();
    }
}

C++写法

#include <iostream>
#include <cmath>

using namespace std;

const int N = 100010, M = 20;
int n, m;
int w[N];
int f[N][M];

void init()
{
    for (int j = 0; j < M; j ++)
        for (int i = 1; i + (1 << j) - 1 <= n; i ++)
            if (j == 0) f[i][j] = w[i];
            else f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}

int query(int l, int r)
{
    int len = r - l + 1;
    int k = log(len) / log(2);
    return max(f[l][k], f[r - (1 << k) + 1][k]);
}

int main()
{
    scanf("%d%d", &n, &m);

    for (int i = 1; i <= n; i ++) scanf("%d", &w[i]);

    init();

    while(m --) 
    {
        int l, r;
        scanf("%d%d", &l, &r);
        printf("%d\n", query(l, r));
    }

    return 0;
}
  • 33
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
抱歉,我不确定您指的是哪种RMQ算法。一般来说,RMQ是“区间最小值查询”(Range Minimum Query)的缩写,其实现算法有多种。以下是两种常见的RMQ算法的实现代码,供参考: 1. 线段树RMQ算法 ```python class SegmentTree: def __init__(self, arr): self.tree = [0] * (4 * len(arr)) self.build(arr, 0, 0, len(arr) - 1) def build(self, arr, index, left, right): if left == right: self.tree[index] = arr[left] else: mid = (left + right) // 2 self.build(arr, index * 2 + 1, left, mid) self.build(arr, index * 2 + 2, mid + 1, right) self.tree[index] = min(self.tree[index * 2 + 1], self.tree[index * 2 + 2]) def query(self, index, left, right, qleft, qright): if left > qright or right < qleft: return float('inf') elif qleft <= left and qright >= right: return self.tree[index] else: mid = (left + right) // 2 return min(self.query(index * 2 + 1, left, mid, qleft, qright), self.query(index * 2 + 2, mid + 1, right, qleft, qright)) # 示例 arr = [1, 3, 2, 7, 9, 11] tree = SegmentTree(arr) print(tree.query(0, 0, len(arr) - 1, 1, 4)) # 输出2,即arr[2:5]的最小值 ``` 2. ST算法 ```python import math class ST: def __init__(self, arr): n = len(arr) k = int(math.log2(n)) self.table = [[0] * (k + 1) for _ in range(n)] for i in range(n): self.table[i][0] = arr[i] for j in range(1, k + 1): for i in range(n - 2 ** j + 1): self.table[i][j] = min(self.table[i][j - 1], self.table[i + 2 ** (j - 1)][j - 1]) def query(self, left, right): k = int(math.log2(right - left + 1)) return min(self.table[left][k], self.table[right - 2 ** k + 1][k]) # 示例 arr = [1, 3, 2, 7, 9, 11] st = ST(arr) print(st.query(1, 4)) # 输出2,即arr[2:5]的最小值 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值