洛谷_3865 ST表

题意

给出一个序列,求某一个区间的最大值。(RMQ)

思路

ST算法,设f[i][j]f[i][j]表示从序列的第ii个位置开始aj个数的最大值,我们可以得到公式f[i][j]=max(f[i][j1],f[i+2j1][j1])f[i][j]=max(f[i][j−1],f[i+2j−1][j−1]),相当于把一个从ii2j的区间分成了两个长度为2j12j−1的区间。当我们查询最大值的时候,我们可以算出一个kk,就是让2k<这个区间长度时,kk的最大值,那么我们查询的区间(l,r)的答案就为max(f[l][k],f[r2k+1][k])max(f[l][k],f[r−2k+1][k]),这两段刚好覆盖了这个区间,所以答案是准确的。

代码

#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,x,y,f[100001][22];
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)scanf("%d",&f[i][0]);//初始化,因为从第i个数开始长度为1的区间的最大值就是它本身
    for(int j=1;j<=20;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]);
    while(m--)  
    {
        scanf("%d%d",&x,&y);
        int k=log2(y-x+1);
        printf("%d\n",max(f[x][k],f[y-(1<<k)+1][k]));
    }
}
### 回答: **ST(Sparse Table)和前缀和都是用于高效查询数据的预处理技术,但它们解决的问题类型、适用场景、时间复杂度和原理完全不同。** 下面从多个维度详细对比它们的区别。 --- ## ✅ 一、核心功能对比 | 特性 | 前缀和(Prefix Sum) | ST(Sparse Table) | |------|------------------------|------------------------| | **主要用途** | 快速计算区间和(或线性组合) | 快速查询静态区间的最值(最大值/最小值) | | **支持操作** | `+`, `-` 等可逆运算 | `max`, `min`(满足幂等性和区间可合并性) | | **是否支持修改** | ❌ 静态数组(不能改) | ❌ 静态数组(完全不可改) | | **查询时间复杂度** | O(1) | O(1) | | **预处理时间复杂度** | O(n) | O(n log n) | | **空间复杂度** | O(n) | O(n log n) | | **典型问题** | 区间求和、子数组和为k等问题 | 静态RMQ(Range Minimum/Maximum Query) | --- ## ✅ 二、工作原理详解 ### 1. 前缀和(Prefix Sum) #### 📌 原理: 通过预先计算前缀累加和,将区间和转化为两个前缀和之差。 ```cpp // 预处理 int prefix[n + 1]; prefix[0] = 0; for (int i = 1; i <= n; ++i) prefix[i] = prefix[i - 1] + arr[i - 1]; // 查询 [l, r] 的和(0-indexed) int sum = prefix[r + 1] - prefix[l]; ``` > ⏱️ 查询:O(1),预处理:O(n) #### ✅ 优点: - 极其简单,速度快 - 可扩展到二维(矩阵前缀和)、异或前缀和等 #### ❌ 局限: - 只适用于**可逆运算**(如 +, -, ^),无法处理 `max`, `min` --- ### 2. ST(Sparse Table) #### 📌 原理: 基于动态规划 + 倍增思想,预处理所有长度为 $2^k$ 的区间的最值。 ```cpp #include <cmath> #include <algorithm> const int MAXN = 100005; const int LOG = 17; int st[MAXN][LOG]; // st[i][k] 示从位置 i 开始,长度为 2^k 的区间的最小值 int log2_table[MAXN]; // 预处理 log2 值加速 // 预处理 log2 值 void build_log() { log2_table[1] = 0; for (int i = 2; i < MAXN; ++i) log2_table[i] = log2_table[i / 2] + 1; } // 构建 ST (以最小值为例) void build_st(int arr[], int n) { for (int i = 0; i < n; ++i) st[i][0] = arr[i]; // 长度为1 for (int j = 1; (1 << j) <= n; ++j) for (int i = 0; i + (1 << j) - 1 < n; ++i) st[i][j] = std::min(st[i][j-1], st[i + (1 << (j-1))][j-1]); } // 查询 [l, r] 区间最小值 int query_min(int l, int r) { int len = r - l + 1; int k = log2_table[len]; return std::min(st[l][k], st[r - (1 << k) + 1][k]); } ``` > ⏱️ 查询:O(1),预处理:O(n log n) #### ✅ 优点: - 支持 O(1) 查询任意区间的 `max` 或 `min` - 不需要修改数据结构(适合静态数组) #### ❌ 局限: - 不支持单点更新或区间修改(一旦数组变化就得重建) - 空间占用较大(O(n log n)) - 仅适用于满足 **幂等性** 和 **重叠可合并性** 的操作(如 `max`, `min`, `gcd`,但不适用于 `sum`) --- ## ✅ 三、关键区别总结 | 维度 | 前缀和 | ST | |------|--------|-------| | **操作性质** | 可逆运算(+ - ^) | 幂等运算(max min gcd) | | **是否允许重叠合并** | 否(必须精确拆分) | 是(可以重叠取 min/max) | | **查询机制** | 差分还原:`S[r] - S[l-1]` | 倍增覆盖:`min(st[l][k], st[r−2^k+1][k])` | | **适用问题举例** | 子数组和等于k、连续段求和 | 滑动窗口最值、LCA中的RMQ、笛卡尔树辅助构造 | | **能否处理非重叠信息?** | ✔️ 能 | ✔️ 能(但设计不同) | | **是否支持动态修改?** | ❌(可用树状数组/Fenwick替代) | ❌(可用线段树替代) | --- ## ✅ 四、使用选择建议 | 场景 | 推荐方法 | |------|----------| | 求区间和、异或和、平均值等 | ✅ 前缀和 | | 求区间最大值、最小值(无修改) | ✅ ST | | 数组会频繁修改 | ❌ 都不行 → 改用 **线段树** 或 **树状数组** | | 多维数组求区域和 | ✅ 二维/三维前缀和 | | 多次查询滑动窗口最值 | ✅ 单调队列 or ST(小窗口) | --- ## ✅ 五、经典例题对比 | 问题 | 解法 | |------|------| | LeetCode 303. Range Sum Query - Immutable | 前缀和 | | LeetCode 239. Sliding Window Maximum | 单调队列(ST也可做,但 O(n log n)) | | P3865 【模板】ST | ST | | Codeforces A. Bear and Prime 100 | 前缀和优化计数 | --- ## ✅ 总结 > 🔺 **前缀和** 是“**累计差分法**”,适用于**线性可逆运算**; > > 🔺 **ST** 是“**倍增极值**”,适用于**静态最值查询**。 两者虽然都能实现 O(1) 查询,但背后的数学基础不同: - 前缀和依赖于运算的**可逆性** - ST依赖于最值运算的**幂等性和可重叠合并性** 因此它们是**互补而非替代**的关系。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值