ST表 区间最大值

ST表 区间最大值

P3865 【模板】ST 表 && RMQ 问题

题目背景

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

题目描述

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

输入格式

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

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

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

输出格式

输出包含 \(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\%\) 的数据,满足 \(1\le N,M\le 10\)

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

对于 \(100\%\) 的数据,满足 \(1\le N\le {10}^5\)\(1\le M\le 2\times{10}^6\)\(a_i\in[0,{10}^9]\)\(1\le l_i\le r_i\le N\)

Response:

这道题目是 ST 表(Sparse Table) 的经典应用,适用于 静态区间最大值(RMQ:Range Maximum Query)问题


✨题目简要说明

给定一个长度为 \(N\) 的数组 \(a\) ,有 \(M\) 次区间查询,每次给出一个区间 \([l_i, r_i]\) ,求这个区间的最大值。


🚀算法思路:ST 表

ST 表是一种用于 静态(不修改)区间最值查询 的数据结构,特点是:

  • 预处理: \(O(N \log N)\)

  • 每次查询:** \(O(1)\) **(非常快)

✅适用条件:

  • 数组不修改(静态查询

  • 多次区间最值查询(最大值/最小值)


🧠基本原理

ST 表的核心思想是:
将数组分成多个长度为 \(2^k\) 的区间,记录每段的最大值。

设:

  • st[i][j] 表示从下标 \(i\) 开始,长度为 \(2^j\) 的区间内的最大值。

转移方程如下:

st[i][j] = max(st[i][j-1], st[i + (1 << (j-1))][j-1])

快速查询:

对于任意区间 \([l, r]\) ,设 \(k = \lfloor \log_2(r - l + 1) \rfloor\)
那么答案是:

max(st[l][k], st[r - (1 << k) + 1][k])

🧩实现步骤

  1. 预处理对数表 log2[i],用于快速计算 \(\lfloor \log_2(i) \rfloor\)

  2. 构建 ST 表。

  3. 查询时用两个长度为 \(2^k\) 的区间拼起来,查询最大值。


✅ACM/OI 风格 C++代码(含详细注释)

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

const int MAXN = 1e5 + 5;
const int LOGN = 17; // log2(1e5) ≈ 17

int a[MAXN];
int st[MAXN][LOGN + 1]; // ST表
int log2_table[MAXN];   // 预处理 log2(i)

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

    // 读入原数组
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        st[i][0] = a[i]; // 初始化 ST 表的第 0 层
    }

    // 预处理 log2(i)
    log2_table[1] = 0;
    for (int i = 2; i <= n; i++) {
        log2_table[i] = log2_table[i / 2] + 1;
    }

    // 构建 ST 表
    for (int j = 1; (1 << j) <= n; j++) { // j 为区间长度的幂次
        for (int i = 1; i + (1 << j) - 1 <= n; i++) { // i 为起点
            st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
        }
    }

    // 多次查询
    while (m--) {
        int l, r;
        scanf("%d%d", &l, &r);
        int k = log2_table[r - l + 1];
        int res = max(st[l][k], st[r - (1 << k) + 1][k]);
        printf("%d\n", res);
    }

    return 0;
}

⏱复杂度分析

  • 预处理 ST 表\(O(N \log N)\)

  • 查询每次\(O(1)\) ,总查询 \(O(M)\)

总时间复杂度: \(O(N \log N + M)\)

完全能满足题目中 \(M \le 2 \times 10^6\) 的强数据要求!


有需要我可以再讲讲怎么用 快速读入 或者扩展为求最小值、区间 GCD。要试试吗?

4o

Prompt:

区间 GCD

Response:

好的!下面我们来讲讲 如何使用 ST 表来处理区间 GCD(最大公约数) 查询问题。


❓问题背景

和区间最大值类似,只不过这次你要处理的操作是:

给定一个数组 \(a\) ,每次查询一个区间 \([l, r]\) ,输出该区间内所有数的 GCD(最大公约数)。


✅ST 表是否适用?

适用!

因为 GCD 运算满足结合律和交换律:

  • \(\gcd(a, \gcd(b, c)) = \gcd(\gcd(a, b), c)\)

而且:
GCD 运算是“可重复合并”的,符合 ST 表的要求。


🧠构建思路

与区间最大值非常类似,只不过把 max 换成 __gcd

定义:

  • st[i][j] 表示从位置 \(i\) 开始,长度为 \(2^j\) 的子数组的 GCD。

状态转移:

st[i][j] = __gcd(st[i][j-1], st[i + (1 << (j - 1))][j - 1]);

查询:

对于任意区间 \([l, r]\) ,设 \(k = \lfloor \log_2(r - l + 1) \rfloor\)
那么答案是:

__gcd(st[l][k], st[r - (1 << k) + 1][k])

✅ACM/OI 风格 C++ 实现(区间 GCD)

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

const int MAXN = 1e5 + 5;
const int LOGN = 17;

int a[MAXN];
int st[MAXN][LOGN + 1];
int log2_table[MAXN];

// 辗转相除法求 gcd
int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}

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

    // 读入原数组
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        st[i][0] = a[i]; // 初始化 ST 表第 0 层
    }

    // 预处理 log2(i)
    log2_table[1] = 0;
    for (int i = 2; i <= n; i++) {
        log2_table[i] = log2_table[i / 2] + 1;
    }

    // 构建 ST 表
    for (int j = 1; (1 << j) <= n; j++) {
        for (int i = 1; i + (1 << j) - 1 <= n; i++) {
            st[i][j] = gcd(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
        }
    }

    // 处理每个查询
    while (m--) {
        int l, r;
        scanf("%d%d", &l, &r);
        int k = log2_table[r - l + 1];
        int res = gcd(st[l][k], st[r - (1 << k) + 1][k]);
        printf("%d\n", res);
    }

    return 0;
}

🔎说明

  • 和最大值查询的模板几乎一样,只是把 max 换成了 gcd

  • __gcd 是 GCC 提供的内建函数,也可以手写 gcd() 函数。


✅时间复杂度

  • 预处理 ST 表: \(O(N \log N)\)

  • 每次查询: \(O(1)\)

posted @ 2025-05-21 16:43  katago  阅读(42)  评论(0)    收藏  举报