题解:洛谷 P3865 ST表 & RMQ问题

【题目来源】

洛谷:P3865 【模板】ST 表 & RMQ 问题 - 洛谷

【题目描述】

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

【输入】

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

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

接下来 \(M\) 行,每行包含两个整数 \(l_i,r_i\),表示查询的区间为$ [l_i,r_i]$。

【输出】

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

【输入样例】

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

【输出样例】

9
9
7
7
9
8
7
9

【核心思想】

  1. 问题分析:给定长度为 \(N\) 的静态数组和 \(M\) 个区间最值查询(RMQ),需要快速回答每个区间 \([l, r]\) 的最大值。若对每个查询暴力扫描,时间复杂度为 \(O(M \cdot N)\)。关键观察是:区间最值具有可重叠性——两个覆盖查询区间的子区间的最值可以合并得到整体最值,因此可以通过预处理实现 \(O(1)\) 查询。

  2. 算法选择

    • ST 表(Sparse Table):一种基于倍增思想的静态区间最值查询数据结构,预处理 \(O(N \log N)\),查询 \(O(1)\)
    • 区间分解:将任意区间 \([l, r]\) 分解为两个长度为 \(2^k\) 的重叠子区间,分别取最值后再合并
  3. 关键步骤

    • 预处理 ST 表
      • 初始化:\(f[i][0] = a_i\)(长度为 \(2^0 = 1\) 的区间最值即自身)
      • \(j\)\(1\)\(\lfloor \log_2 N \rfloor\)
        • \(i\)\(1\)\(N - 2^j + 1\)
          • \(f[i][j] = \max(f[i][j-1], f[i + 2^{j-1}][j-1])\)
          • 含义:区间 \([i, i+2^j-1]\) 的最值 = 左半区间 \([i, i+2^{j-1}-1]\) 与右半区间 \([i+2^{j-1}, i+2^j-1]\) 的最值之较大者
    • 回答查询 \([l, r]\)
      • 计算 \(k = \lfloor \log_2(r - l + 1) \rfloor\)
      • 答案 = \(\max(f[l][k], f[r - 2^k + 1][k])\)
      • 两个子区间分别为 \([l, l+2^k-1]\)\([r-2^k+1, r]\),它们重叠但共同覆盖 \([l, r]\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log N + M)\),预处理 \(O(N \log N)\),每次查询 \(O(1)\)
    • 空间复杂度:\(O(N \log N)\),存储 ST 表 \(f[N][\log N]\)
  5. ST 表的核心思想

    • 倍增预处理:通过 \(f[i][j]\) 表示从 \(i\) 开始长度为 \(2^j\) 的区间最值,利用递推式将大问题分解为两个已解决的子问题,体现了动态规划的最优子结构
    • 可重叠性利用:最值查询允许区间重叠,即 \(\max([l, r]) = \max(\max([l, l+2^k-1]), \max([r-2^k+1, r]))\)。两个长度为 \(2^k\) 的区间覆盖 \([l, r]\) 且可能重叠,但由于最值运算的幂等性(\(\max(a, a) = a\)),重叠不影响结果
    • 对数分块:通过 \(\log_2\) 将任意区间长度映射到最大的 \(2\) 的幂次,使得任意区间都能被两个等长的幂次长度区间覆盖
    • 静态数据结构:ST 表适用于数组不修改、只查询的场景。若需要修改,需改用线段树等动态数据结构
    • 适用于静态 RMQ 问题、区间最值/最小值查询、LCP(最长公共前缀)等场景,是倍增思想在数据结构中的经典应用

【算法标签】

普及 #ST表

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int MAX_N = 100005;  // 定义数组最大长度
const int MAX_LOG = 22;    // 最大对数级别
int n, m;                  // n: 数组长度, m: 查询次数
int f[MAX_N][MAX_LOG];     // ST表,f[i][j]表示区间[i,i+2^j-1]的最大值

int main()
{
    // 输入数组长度和查询次数
    cin >> n >> m;

    // 输入原始数组数据
    for (int i = 1; i <= n; i++)
        scanf("%d", &f[i][0]);  // 初始化ST表第一层

    // 预处理ST表
    for (int j = 1; j <= 20; j++)          // 枚举区间长度(2^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]);
        }
    }

    // 处理查询
    for (int i = 1; i <= m; i++)
    {
        int l, r;
        scanf("%d%d", &l, &r);  // 输入查询区间

        // 计算区间长度的对数
        int k = log2(r - l + 1);

        // 查询区间最大值:取两个覆盖区间的最大值
        printf("%d\n", max(f[l][k], f[r - (1 << k) + 1][k]));
    }

    return 0;
}

【运行结果】

8 8
9 3 1 7 5 6 0 8
1 6
9
1 5
9
2 7
7
2 6
7
1 8
9
4 8
8
3 7
7
1 8
9
posted @ 2026-07-02 22:44  团爸讲算法  阅读(14)  评论(0)    收藏  举报