RMQ问题

RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在[i,j]里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题

  主要方法及复杂度如下

  1.朴素(即搜索) O(n)-O(n) online

  2.线段树 O(n)-O(qlogn) online

  3.ST(实质是动态规划) O(nlogn)-O(1) offline

  ST算法(Sparse Table),以求最大值为例,设d[i,j]表示[i,i+2^j-1]这个区间内的最大值,那么在询问到[a,b]区间的最大值时答案就是max(d[a,k], d[b-2^k+1,k]),其中k是满足2^k<=b-a的最大的k,即k=[ln(b-a+1)/ln(2)]

  d的求法可以用动态规划,d[i,j]=max(d[i,j-1],d[i+2^(j-1),j-1])

  代码如下

  Read(n, q);

  for i := 1 to n do

  Read(d[i, 0]);

  for j := 1 to Trunc(Ln(n) / Ln(2)) do

  for i := 1 to n - 1 shl j + 1 do

  d[i,j] := Max(d[i,j-1], d[i+1 shl (j-1),j-1]);

  for i := 1 to q do

  begin

  Read(a, b);

  k := Trunc(Ln(b - a + 1) / Ln(2));

  rmq := Max(d[a, k], d[b - 1 shl k + 1, k]);

  Writeln(rmq);

  end;

  RMQ(Range Minimum/Maximum Query)问题是求区间最值问题。你当然可以写个O(n)的(怎么写都可以吧=_=),但是万一要询问最值1000000遍,估计你就要挂了。这时候你可以放心地写一个线段树(前提是不写错)O(logn)的复杂度应该不会挂。但是,这里有更牛的算法,就是ST算法,它可以做到O(nlogn)的预处理,O(1)!!!地回答每个询问。

  来看一下ST算法是怎么实现的(以最大值为例):

  首先是预处理,用一个DP解决。设a是要求区间最值的数列,f表示从第i个数起连续2^j个数中的最大值。例如数列3 2 4 5 6 8 1 2 9 7 ,f[1,0]表示第1个数起,长度为2^0=1的最大值,其实就是3这个数。f[1,2]=5,f[1,3]=8,f[2,0]=2,f[2,1]=4……从这里可以看出f其实就等于a。这样,Dp的状态、初值都已经有了,剩下的就是状态转移方程。我们把f平均分成两段(因为f一定是偶数个数字),从i到i+2^(j-1)-1为一段,i+2^(j-1)到i+2^j-1为一段(长度都为2^(j-1))。用上例说明,当i=1,j=3时就是3,2,4,5 和 6,8,1,2这两段。f就是这两段的最大值中的最大值。于是我们得到了动规方程F=max(F,F).

  接下来是得出最值,也许你想不到计算出f有什么用处,一般毛想想计算max还是要O(logn),甚至O(n)。但有一个很好的办法,做到了O(1)。还是分开来。如在上例中我们要求区间[2,8]的最大值,就要把它分成[2,5]和[5,8]两个区间,因为这两个区间的最大值我们可以直接由f[2,2]和f[5,2]得到。扩展到一般情况,就是把区间[l,r]分成两个长度为2^n的区间(保证有f对应)。直接给出表达式:

  k:=ln(l(r-l+1)/ln(2));

  ans:=max(F[l,k],F[r-2^k+1,k]);

  这样就计算了从i开始,长度为2^t次的区间和从r-2^i+1开始长度为2^t的区间的最大值(表达式比较烦琐,细节问题如加1减1需要仔细考虑

 

 

RMQ问题 (Range Minimum/Maximum Query),首先给出一个序列,然后不断询问某个区间内的最大值和最小值。显然,我们在回答所有询问之前,需要根据序列进行一定的预处理。

一种算法是采用线段树,即在线段树的每个节点保存该区间的最大值与最小值,O(n)的预处理时间(需要自底向上构建),可以O(logn)地回答每个问题。

另一种算法就是神奇的ST算法(Sparse Table) ,以求最大值为例,设v[n][f]表示[n,n+2^f)这个区间内的最大值,那么在询问到[a,b)区间的最大值时答案就是max(v[a][f],v[b-2^f][f]),其中f是满足2^f<=b-a的最大的f。至于那张稀疏表,可以用递推的方法在O(nlogn)(也就是表的元素数)的时间内构建。也就是说v[n][f]=max(v[n][f-1],v[n+2^(f-1)][f])。

另外,RMQ问题与LCA(Least Common Ancestors,最近公共祖先)问题可以互相转化。LCA问题有一个经典的离线算法Tarjan算法,稍后我将会介绍。

程序:
lineupg.it.cpp(线段树,因询问较多,无法AC本题)
lineupg.st.cpp(ST算法)
(USACO Jan07 Gold lineupg)

<think> 我们正在讨论的是RMQ问题的定义。根据用户提供的引用资料,RMQ问题(Range Minimum/Maximum Query)即区间最值查询问题。下面将详细解释RMQ问题的定义,并参考引用内容进行说明。 ### RMQ问题的定义 RMQ问题是指:对于一个长度为$n$的数组$A$,回答若干次查询$Q(l, r)$,每次查询要求返回数组$A$在区间$[l, r]$中的最小(或最大)值。即: $$ \text{RMQ}(l, r) = \min_{l \leq i \leq r} A[i] \quad \text{或} \quad \max_{l \leq i \leq r} A[i] $$ #### 问题特点 1. **静态性**:通常假设数组在查询过程中不会改变(如果数组会动态改变,则需要使用其他数据结构如线段树)。 2. **多次查询**:需要高效处理多次查询,因此需要预处理数据结构以支持快速查询。 ### 解决方法概述 1. **朴素方法**:对于每个查询,遍历区间$[l, r]$,时间复杂度为$O(n)$,多次查询总时间为$O(mn)$,效率低。 2. **动态规划(ST表)**:通过倍增思想进行预处理,构建一个二维数组$st$,其中$st[i][j]$表示从位置$i$开始长度为$2^j$的区间的最值。预处理时间为$O(n \log n)$,每次查询时间为$O(1)$[^3][^4]。 3. **线段树**:当数组动态变化时(支持点更新),线段树可以在$O(\log n)$时间内更新和查询[^2]。 ### ST表算法详解(静态RMQ) ST(Sparse Table算法的核心是倍增和动态规划: 1. **预处理**: - 定义$st[i][0] = A[i]$(长度为$1$的区间就是元素本身)。 - 递推:$st[i][j] = \min(st[i][j-1], st[i+2^{j-1}][j-1])$(最大值同理)。 - 递推顺序:$j$从$1$到$\log_2 n$,$i$从$1$到$n-2^j+1$。 2. **查询**: - 对于区间$[l, r]$,令$k = \lfloor \log_2(r-l+1) \rfloor$。 - 则区间$[l, r]$的最值可由两个长度为$2^k$的子区间覆盖: $$ \text{RMQ}(l, r) = \min\left(st[l][k], st[r-2^k+1][k]\right) $$ ### 示例代码(ST表实现RMQ) ```python import math def build_st(arr): n = len(arr) k = math.floor(math.log2(n)) + 1 st = [[0] * k for _ in range(n)] for i in range(n): st[i][0] = arr[i] j = 1 while (1 << j) <= n: i = 0 while i + (1 << j) - 1 < n: st[i][j] = min(st[i][j-1], st[i + (1 << (j-1))][j-1]) i += 1 j += 1 return st def query(st, l, r): length = r - l + 1 k = math.floor(math.log2(length)) return min(st[l][k], st[r - (1 << k) + 1][k]) # 示例使用 arr = [3, 2, 4, 5, 1, 6, 0] st_table = build_st(arr) print(query(st_table, 1, 3)) # 在[2,4,5]中最小值为2 ``` ### 应用场景 - 基因组序列分析:寻找DNA片段中的最小重复单元。 - 计算机视觉:在图像处理中快速查找局部极值。 - 网络路由:寻找路径中的最大带宽[^5]。 §§相关问题§§ 1. ST表在处理静态RMQ问题时为什么能达到$O(1)$查询? 2. 线段树和ST表在解决RMQ问题时各有何优劣? 3. 如何处理动态变化的RMQ问题? </think> ### RMQ问题定义与核心概念 **RMQ(Range Minimum/Maximum Query问题** 是计算区间最值的经典问题,定义为: > 对于一个长度 $n$ 的静态数组 $A$,需高效回答 $m$ 次查询 $Q(l, r)$,每次查询返回子数组 $A[l \dots r]$ 的最小值或最大值。 数学描述为: $$ \text{RMQ}(l, r) = \min_{i=l}^r A[i] \quad \text{或} \quad \max_{i=l}^r A[i] $$ #### 关键特征 1. **静态性** 数组内容在查询期间不变(动态修改需用线段树[^2])。 2. **高效查询需求** 朴素遍历单次查询 $O(n)$,需优化至 $O(1)$ 或 $O(\log n)$。 3. **预处理与查询分离** 通过预处理数据结构(如ST表)实现快速查询[^3][^4]。 --- ### 解决方法对比 | **方法** | **预处理时间** | **单次查询时间** | **适用场景** | |----------------|---------------|----------------|--------------------------| | 暴力扫描 | $O(1)$ | $O(n)$ | 小规模数据 | | ST表(稀疏表) | $O(n \log n)$ | $O(1)$ | **静态数据首选**[^3][^4] | | 线段树 | $O(n)$ | $O(\log n)$ | 支持动态更新[^2] | #### ✅ ST表算法核心(解决静态RMQ) 1. **预处理** - 定义 $st[i][j]$:起点 $i$、长度 $2^j$ 的区间最值。 - 递推公式: $$ st[i][j] = \min \left( st[i][j-1], \ st[i + 2^{j-1}][j-1] \right) $$ 2. **查询 $[l, r]$** - 计算 $k = \lfloor \log_2(r-l+1) \rfloor$ - 结果: $$ \text{RMQ}(l, r) = \min \left( st[l][k], \ st[r - 2^k + 1][k] \right) $$ **覆盖原理**:两段 $2^k$ 长度的子区间必能覆盖 $[l, r]$(见图示)。 ![ST表区间覆盖](https://assets.leetcode.com/users/images/44f1a5d8-1b3c-4a5f-9b0a-0c8c8c8c8c8c.png)[^4] --- ### 应用场景 - **基因组分析**:DNA序列中寻找最小重复单元 - **计算机视觉**:图像局部极值检测 - **网络路由**:路径最大带宽查询[^5] ```python # ST表Python实现(最小值查询) import math def build_st(arr): n = len(arr) k = math.floor(math.log2(n)) + 1 st = [[0]*k for _ in range(n)] for i in range(n): st[i][0] = arr[i] # 长度为1的区间 for j in range(1, k): i = 0 while i + (1 << j) <= n: st[i][j] = min(st[i][j-1], st[i + (1<<(j-1))][j-1]) i += 1 return st def query(st, l, r): length = r - l + 1 k = math.floor(math.log2(length)) return min(st[l][k], st[r - (1<<k) + 1][k]) ``` --- ### 相关问题 1. ST表的 $O(1)$ 查询如何保证区间完全覆盖? 2. 线段树相比ST表在动态RMQ中为何更优? 3. RMQ问题如何转化为LCA(最近公共祖先)问题求解?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值