RMQ总结
〇、注意
本文中的 log \log log 都是以 2 2 2 为底。
一、使用范围
RMQ 是处理区间最值的一种高效算法。
二、算法描述
我们以 数列区间最大值 为例。
很容易想到,直接枚举 [ l , r ] [l,r] [l,r] 求出最大值,时间复杂度为 O ( N M ) O(NM) O(NM)。
RMQ 是怎么处理的呢?定义 d p i , j dp_{i,j} dpi,j 表示从 i i i 出发,长度为 2 j 2^j 2j 的序列最大值。
-
初始化
刚开始时, d p i , 0 = a i ( 1 ≤ i ≤ N ) dp_{i,0}=a_i(1 \le i \le N) dpi,0=ai(1≤i≤N)。因为 2 0 = 1 2^0 = 1 20=1,就是每个元素它自己,肯定是它自己最大。 -
计算
对于任意 d p i , j ( i ≠ j 且 i < j ) dp_{i,j}(i \neq j 且 i<j) dpi,j(i=j且i<j),把它看作长度均为 2 j − 1 2^{j-1} 2j−1 的 [ i , i + 2 j − 1 − 1 ] [i,i+2^{j-1} - 1] [i,i+2j−1−1] 和 [ i + 2 j − 1 , i + 2 j − 1 ] [i+2^{j-1},i+2^j-1] [i+2j−1,i+2j−1] 两部分合并取最大值。所以 d p i , j = max ( d p i , j − 1 , d p i + 2 j − 1 , j − 1 ) dp_{i,j}=\max(dp_{i,j-1},dp_{i+2^{j-1},j-1}) dpi,j=max(dpi,j−1,dpi+2j−1,j−1)。其中 j j j 是作为转移的阶段,因为 j j j 由 j − 1 j -1 j−1 转移而来,所以要放在第一层循环。
让我们来计算时间复杂度。首先 j j j 的上限是 log N \log N logN,里面 i i i 从 1 1 1 到 N − 2 j + 1 N - 2^{j} + 1 N−2j+1,约为 N N N 次。所以,预处理的时间复杂度为 O ( N log N ) O(N\log N) O(NlogN)。 -
查询方法
对于查询区间 [ l , r ] [l,r] [l,r],我们的答案就是 max ( d p l , k , d p r − 2 k + 1 , k ) \max(dp_{l,k},dp_{r-2^k+1,k}) max(dpl,k,dpr−2k+1,k) ,其中 k = log ( r − l + 1 ) k=\log(r-l+1) k=log(r−l+1)。因为 d p l , k dp_{l,k} dpl,k 是 [ l , l + 2 k − 1 ] [l,l+2^k-1] [l,l+2k−1], d p r − 2 k + 1 , k dp_{r-2^k+1,k} dpr−2k+1,k 是 [ r − 2 k + 1 , r ] [r-2^k+1,r] [r−2k+1,r],只需证明 l + 2 k − 1 ≥ r − 2 k + 1 l+2^k-1 \geq r-2^k+1 l+2k−1≥r−2k+1 即可。这样每个元素都参与了计算。证明:
设 l + 2 k − 1 ≥ r − 2 k + 1 成立 设 l+2^k-1 \geq r-2^k+1 成立 设l+2k−1≥r−2k+1成立
∴ 2 × 2 k ≥ r − l + 2 \therefore 2\times 2^{k} \geq r-l+2 ∴2×2k≥r−l+2
将 k = log ( r − l + 1 ) 代入得: 将k=\log(r-l+1)代入得: 将k=log(r−l+1)代入得:
2 ( r − 1 + 1 ) ≥ r − l + 2 2(r-1+1)\ge r-l+2 2(r−1+1)≥r−l+2
∴ r − l ≥ 0 \therefore r-l\ge 0 ∴r−l≥0
∴ r ≥ l \therefore r\ge l ∴r≥l
这是我们默认的。所以假设成立。 这是我们默认的。所以假设成立。 这是我们默认的。所以假设成立。
得证。 得证。 得证。
这样,我们就只用 O ( 1 ) O(1) O(1) 的时间复杂度就可以进行查询了。
总的时间复杂度就是 O ( N log N + M ) O(N\log N+M) O(NlogN+M)。
参考代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int Maxn = 1e5 + 5;
int n, m, x, y, a[Maxn], dp[Maxn][20];
int main() {
scanf("%d %d", &n, &m);
for(int i = 1;i <= n; ++i) scanf("%d", &a[i]);
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]);
while(m--) {
scanf("%d %d", &x, &y);
int k = log2(y - x + 1);
printf("%d\n", max(dp[x][k], dp[y - (1 << k) + 1][k]));
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!