当前位置: 首页 > ops >正文

【CSP-S】数据结构 ST 表详解

作者要备战 Csp-s 比赛,大家可以查看相关的专栏 传送门

ST表可以干什么

ST 表主要用于求解静态区间 RMQ 问题,静态区间 RMQ 问题就是给你一个数组,然后给你几个区间,计算区间和,区间最大值,区间最小值等问题

ST 表的优势

ST 表可以快速查询区间 RMQ 问题。需要一个 O(nlog⁡n)O(n \log n)O(nlogn) 的预处理,但是单次查询只要 O(1)O(1)O(1) ,对于线段树和树状数组这两个数据结构的查询 O(log⁡n)O(\log n)O(logn) 他会更快!

其次 ST 表编程简单,代码量相对于 线段树和树状数组简单 而且常数更小

ST 表的劣势

ST 表只可以求解静态 RMQ 问题,不支持修改操作。而且 ST 表只支持可重复性运算(这个后面会讲到)

ST 表的实现原理

Step1: 预处理

ST 表主要是基于倍增的思想进行预处理。

定义

我们设 dp[start][size]dp[start][size]dp[start][size] 表示从 startstartstart 开始,
区间长度为 2size2^{size}2size 的答案。

实现

那么我们怎么处理这个 dpdpdp 数组呢?我们考虑使用倍增的方法实现
公式
dp[start][size]=dp[start][size−1]+dp[start+2size−1][size−1]dp[start][size] = dp[start][size - 1] + dp[start + 2^{size-1}][size-1]dp[start][size]=dp[start][size1]+dp[start+2size1][size1]

下面进行一些推导
当前的区间 dp[start][size]dp[start][size]dp[start][size] 可以看成两个区间的合并而成。


第一个区间dp[start][size−1]dp[start][size-1]dp[start][size1]
startstartstart 位置开始,长度为 2size−12^{size-1}2size1 的区间


第二个区间: dp[start+2size−1][size−1]dp[start + 2^{size - 1}][size-1]dp[start+2size1][size1]
start+2size−1start + 2^{size - 1}start+2size1 开始,长度为 2size−12^{size-1}2size1


其实很简单的一个道理
长度为 2size2^{size}2size 的区间一定会由两个长度为 2size−12^{size-1}2size1 的区间合并得到。

因为 2size−1+2size−1=2size2^{size-1} + 2^{size-1} = 2^{size}2size1+2size1=2size
如果还是不懂,我们可以证明一下 2x−1+2x−1=2x2^{x-1} + 2^{x-1} = 2^{x}2x1+2x1=2x
证明:
左边=2x−1+2x−1=2x−1×2=2x左边 = 2^{x-1} + 2^{x-1} = 2^{x-1} \times 2 = 2^x左边=2x1+2x1=2x1×2=2x

只是将第二个区间起点的位置变化成了第一个区间起点 + 第一个区间长度而已。

代码
for (int size = 1; size <= Log2[n]; size++) // Log2[i] 表示 log2(i) 是多少for (int start = 1; start + (1 << size) <= n; start++) // 枚举开始位置ST[start][size] = max(ST[start][size - 1], ST[start + (1 << (size - 1))][size - 1]);

Step2: 查询

这里我们要考虑如何查询。
首先我们发现一个观点,任何一个区间都可以化成两个长度为 2i2^i2i 的区间相加

比如区间 [5,11][5, 11][5,11] 可以化为 [5,8][5, 8][5,8](长度为 444222^222) 和 [8,11][8, 11][8,11](长度为 444222^222)

但是我们会发现,这里面会有一些元素计算过两次!比如 888,所以 ST 表只可以计算 区间最大值,区间最小值 因为一个数就算比较很多遍不影响最大最小值 但是 ST 表不可以计算区间和 因为会有元素计算了 222

所以我们可以正着查询一次,然后倒着查询一次即可

什么意思?
就是指我们查询当前的左边界可以延伸到的最长位置(不超过右边界)
然后我们再次查询当前的右边界可以向左延伸的最长位置(不超过左边界)
两个答案相加即为最终的答案

注意区间是从左到右的,在进行步骤 2 时要减去区间长度

代码
int s = Log2[r - l + 1];
printf("%d\n", max(ST[l][s], ST[r - (1 << s) + 1][s]));

例题 区间最大值问题

题目位置:https://www.luogu.com.cn/problem/P3865
代码:

#include <stdio.h>
#include <ctype.h>//#define getchar getchar
//int isdigit(char x) {return ((x) >= '0' && (x) <= '9');}int read() {int ret = 0;char ch;while (!isdigit(ch = getchar()));do {ret = ret * 10 + ch - '0';} while (isdigit(ch = getchar()));return ret;
}#define N ((int) 1e5 + 10)
int Log2[N];
int n, m;void InitLog2(void)
{Log2[1] = 0; // 2^0 = 1for (int i = 2; i <= n + 1; i++) Log2[i] = Log2[i / 2] + 1;
}
int max(int a, int b) {return a > b ? a : b;}int ST[N][18];
int arr[N];void InitSt(void)
{for (int i = 1; i <= n; i++) ST[i][0] = arr[i]; // 区间长度为 1for (int size = 1; size <= Log2[n]; size++)for (int start = 1; start + (1 << size) <= n; start++) // 枚举开始位置ST[start][size] = max(ST[start][size - 1], ST[start + (1 << (size - 1))][size - 1]);
}int main()
{n = read(), m = read();
//    n = read();for (int i = 1; i <= n; i ++) arr[i] = read();InitLog2(), InitSt();while (m--) {int l = read(), r = read();int s = Log2[r - l + 1];printf("%d\n", max(ST[l][s], ST[r - (1 << s) + 1][s]));}return 0;
} 
http://www.xdnf.cn/news/20245.html

相关文章:

  • 植物大战僵尸融合版安装包,下载安装教程
  • PCDN工作原理的详细步骤
  • Netty从0到1系列之EventLoopGroup
  • Kafka面试精讲 Day 10:事务机制与幂等性保证
  • CUDA默认流的同步行为
  • 项目升级--kafka消息队列的应用
  • 状压 dp --- 数据范围小
  • 雪球科技Java开发工程师笔试题
  • happen-before原则
  • WSL Ubuntu Docker 代理自动配置教程
  • LeetCode 139. 单词拆分 - 动态规划解法详解
  • 【软考架构】第二章 计算机系统基础知识:计算机网络
  • 主数据系统是否对于企业是必需的?
  • 最大似然估计:损失函数的底层数学原理
  • 基本数据类型和包装类的区别?
  • 2025年大数据专业人士认证发展路径分析
  • MySQL运维补充
  • 【目录-判断】鸿蒙HarmonyOS开发者基础
  • 敏捷scrum管理实战经验总结
  • 贪心算法应用:化工反应器调度问题详解
  • 【LLIE专题】SIED:看穿0.0001lux的极致黑暗
  • NPU边缘推理识物系统
  • 懒加载的概念
  • 新能源风口正劲,“充电第一股”能链智电为何掉队?
  • 操作系统启动过程详解
  • Coze源码分析-资源库-删除插件-前端源码-核心组件实现
  • 03-生产问题-慢SQL-20250926
  • 机器人控制器开发(导航算法——导航栈关联坐标系)
  • 创客匠人:什么是“好的创始人IP”
  • 2025年本体论:公理与规则的挑战与趋势