每周一算法:倍增法求区间最大最小值(RMQ)

RMQ

RMQ 是英文 Range Maximum/Minimum Query 的缩写,表示区间最大(最小)值。使用倍增思想解决 RMQ 问题的方法是 ST 表(Sparse Table, 稀疏表 )。ST 表是用于解决 可重复贡献问题 的数据结构。

可重复贡献问题 是指对于运算 opt ⁡ \operatorname{opt} opt,满足 x opt ⁡ x = x x\operatorname{opt} x=x xoptx=x,则对应的区间询问就是一个可重复贡献问题。例如,最大值有 max ⁡ ( x , x ) = x \max(x,x)=x max(x,x)=x, g c d gcd gcd 有 gcd ⁡ ( x , x ) = x \operatorname{gcd}(x,x)=x gcd(x,x)=x,所以 RMQ 和区间 GCD 就是一个可重复贡献问题。像区间和就不具有这个性质,如果求区间和的时候采用的预处理区间重叠了,则会导致重叠部分被计算两次。另外, opt ⁡ \operatorname{opt} opt 还必须满足结合律才能使用 ST 表求解。

题目链接

题目链接:【模板】ST 表

题目描述

这是一道 ST 表经典题------静态区间最大值

请注意最大数据时限只有 0.8s,数据强度不低,请务必保证你的每次查询复杂度为 O ( 1 ) O(1) O(1)。若使用更高时间复杂度算法不保证能通过。

如果您认为您的代码时间复杂度正确但是 TLE,可以尝试使用快速读入:

cpp 复制代码
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

函数返回值为读入的第一个整数。

快速读入作用仅为加快读入,并非强制使用。

题目描述

给定一个长度为 N N N 的数列,和 M M M 次询问,求出每一次询问的区间内数字的最大值。

输入格式

第一行包含两个整数 N , M N,M N,M,分别表示数列的长度和询问的个数。

第二行包含 N N N 个整数(记为 a i a_i ai),依次表示数列的第 i i i 项。

接下来 M M M 行,每行包含两个整数 l i , r i l_i,r_i li,ri,表示查询的区间为 l i , r i l_i,r_i li,ri

输出格式

输出包含 M M M 行,每行一个整数,依次表示每一次询问的结果。

样例 #1

样例输入 #1

复制代码
8 8
9 3 1 7 5 6 0 8
1 6
1 5
2 7
2 6
1 8
4 8
3 7
1 8

样例输出 #1

复制代码
9
9
7
7
9
8
7
9

提示

对于 30 % 30\% 30% 的数据,满足 1 ≤ N , M ≤ 10 1\le N,M\le 10 1≤N,M≤10。

对于 70 % 70\% 70% 的数据,满足 1 ≤ N , M ≤ 10 5 1\le N,M\le {10}^5 1≤N,M≤105。

对于 100 % 100\% 100% 的数据,满足 1 ≤ N ≤ 10 5 1\le N\le {10}^5 1≤N≤105, 1 ≤ M ≤ 2 × 10 6 1\le M\le 2\times{10}^6 1≤M≤2×106, a i ∈ 0 , 10 9 a_i\in0,{10}\^9 ai∈0,109, 1 ≤ l i ≤ r i ≤ N 1\le l_i\le r_i\le N 1≤li≤ri≤N。

算法思想

ST 表基于倍增 思想,可以做到 O ( n log ⁡ n ) O(n\log n) O(nlogn) 预处理, O ( 1 ) O(1) O(1) 回答每个询问。但是不支持修改操作

基于倍增思想,考虑如何求出区间最大值。可以发现,如果按照一般的倍增流程,每次跳 2 i 2^i 2i步的话,询问时的复杂度仍旧是 O ( log ⁡ n ) O(\log n) O(logn),效率较低。

由于区间最大值是一个具有可重复贡献 性质的问题。哪怕用来求解的预处理区间有重叠部分,只要这些区间合并是所求的区间,最终计算出的答案就是正确的。举个例子:

区间 2 , 5 2,5 2,5的最大值为 5 5 5,区间 4 , 7 4,7 4,7的最大值为 7 7 7,区间 2 , 7 2,7 2,7的最大值为 max ⁡ { 5 , 7 } = 7 \max\{5,7\}=7 max{5,7}=7。

通过ST表,使用至多两个预处理过的区间就可以覆盖询问区间,也就是说询问时的时间复杂度可以被降至 O ( 1 ) O(1) O(1),在处理有大量询问的题目时十分有效。

预处理ST表

状态表示

  • f i j fij fij表示区间 i , i + 2 j − 1 i,i+2\^j-1 i,i+2j−1的最大值。

状态计算

要计算区间 i , i + 2 j − 1 i,i+2\^j-1 i,i+2j−1的最大值,区间大小为 2 j 2^j 2j,相当于从位置 i i i跳了 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,大小均为 2 j − 1 2^{j-1} 2j−1,如下图所示:

那么状态转移方程:

f i j = m a x { f i j − 1 , f i + 2 j − 1 j − 1 } fij=max\{fij-1,fi+2\^{j-1}j-1\} fij=max{fij−1,fi+2j−1j−1}

初始状态

  • f i 0 = a i fi0=a_i fi0=ai

查询区间最值

对于每个询问 L , R L,R L,R,把它成两个部分 L , L + 2 k − 1 L,L+2\^k-1 L,L+2k−1 R − 2 k + 1 , R R-2\^k+1,R R−2k+1,R,其中 k = ⌊ l o g 2 ( R − L + 1 ) ⌋ k=\lfloor log_2(R-L+1)\rfloor k=⌊log2(R−L+1)⌋,两部分的最值就是答案。

时间复杂度

  • 预处理 ST 表的时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)
  • 回答每个询问的时间复杂度 O ( 1 ) O(1) O(1)

代码实现

cpp 复制代码
#include <iostream>
#include <cmath>
using namespace std;
const int N = 1e5 + 10, M = 20;
int n, a[N], f[N][M];
//创建ST表
void create() {
    //初始状态
    //f[i][0]表示从i开始长度为2^0的区间最值为a[i]本身
    for(int i = 1; i <= n; i ++) f[i][0] = a[i];
    int k = log2(n);
    //枚举区间长度指数j
    for(int j = 1; j <= k; j ++)
        for(int i = 1; i + (1 << j) - 1 <= n; i ++)
            f[i][j] = max(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}
//利用ST表查询区间[L,R]的最大值
int query(int L, int R) {
    int k = log2(R - L + 1);
    return max(f[L][k], f[R - (1 << k) + 1][k]);
}
int main()
{
    int m;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) scanf("%d", a + i);;
    create();
    while(m --) {
        int L, R;
        scanf("%d%d", &L, &R);
        printf("%d\n", query(L, R));
    }
}
相关推荐
折哥的程序人生 · 物流技术专研7 小时前
Java面试85题图解版 · 特别篇:2026后端高频面试题复盘(算法底层逻辑+高并发架构设计全解析,附Java实战代码)
java·网络·数据库·算法·面试
玖玥拾7 小时前
C/C++ 基础笔记(十四)多态与模板编程
c语言·c++·多态·模板
想吃火锅10058 小时前
【leetcode】14.最长公共前缀js
算法·leetcode·职场和发展
Roann_seo%8 小时前
C++文件操作完全指南:从文本读写到二进制文件处理
开发语言·c++
坚果派·白晓明9 小时前
【鸿蒙PC】SDL3 适配:AtomCode + Skills 快速集成 NAPI 测试工具
c++·华为·ai编程·harmonyos·atomcode
云絮.9 小时前
数据库操作
数据库·mysql·算法·oracle
小林ixn9 小时前
LeetCode 206. 反转链表(迭代 + 递归详解)
算法·leetcode·链表
凡人叶枫9 小时前
Effective C++ 条款17:以独立语句将 newed 对象置入智能指针
java·linux·开发语言·c++·算法
凡人叶枫11 小时前
Effective C++ 条款16:成对使用 new 和 delete 时要采取相同形式
开发语言·c++·effective c++
菜鸟‍11 小时前
LeetCode 1 27 和 704 || 两数之和 移除元素 二分查找
算法·leetcode·职场和发展