数据结构之线性结构(完结)——ST表(稀疏表)
yund56 2025-05-11 01:46 4 浏览
某次考试结束后,同学们在班级里比拼各自的考试成绩,很快每个小组的小组长都知道了自己组同学的最高分。
放学后,各名组长同学在跟父母聊天时,透露出本次考试时自己组同学的最高分,父母暗暗记下了分数。
晚上,家长群里,几位组长家长在群里分享出孩子所在组的最高分,一合计,就知道班级最高分是多少了。
上述情境中,家长们并不知道所有同学的成绩,只通过几个小组最高分的比较,就知道了整个班级的最高分,这就是本期将要介绍的ST表(Sparse Table)的应用。
ST表是一种简单的线性结构,主要用来解决静态的区间最值问题(RMQ问题,即Range Minimum/Maximum Query)。
RMQ问题,就是给定一个长度为n的数组,进行m次查询,每次查询区间[L,R]内的最值、极差、最大公约数等。这是一个具有“可重复贡献”性质的问题,即询问的区间存在重叠。
如果直接暴力搜索区间内的最值,其时间复杂度为O(mn),在n和m的范围超过10000时效率会非常低。
而使用ST表可以在O(1)的时间复杂度查询到某区间的最值,但由于预处理ST表的时间复杂度为O(nlogn),因此整体算法的时间复杂度也为O(nlogn)。
模拟生成ST表
前文的情境已经体现了ST表的核心:如果一个大区间能被多个小区间覆盖,那么大区间的最值,就是所有小区间的最值集合中的最值。
小区间的最值如何计算呢?它肯定又能被更小的2个或多个区间覆盖,再去求更小区间的最值。
不断递归拆分区间,直到区间里只有一个元素后,区间的最值就是元素自身,再往回计算递推回去。
以数组{9,3,1,7,5,6,0,8}为例,使用表格展示将区间一分为二求最小值的维护过程:
如果要求区间[5,8]的最小值,直接取第二个区间长度为4的最小值,即0;
但如果要求区间[2,5]的最小值,按照的表,需要取[2]、[3,4]、[5]这3个区间的最小值进行比较,然后得到答案为1,显然与直接遍历区间找最小值相比快不了多少。
因此,我们的表格需要改进,不能从大区间出发进行拆分,要从小区间开始,依次合并相邻两个区间来计算较大区间的最小值,最终计算出整个数组的最小值。
尽管是合并相邻区间,但是结合倍增法的优势,我们只需要计算长度为2的幂次方数的区间最值即可。最终改进后的区间维护表格如下:
在这张表格中,我们就能很快地找到区间[2,5]的最小值,也就是第二个区间长度4的值,即1。
如果要在这张表格里找区间[3,8]的最小值怎么办呢?我们可以让区间[3,6]和区间[5,8]的最小值,即第3和第5个区间长度为4的最小值作比较,两个区间的并集恰好是[3,8]。
这里我们可以发现,子区间的重合不影响大区间的结果,这也是ST表的适用场合。
同时,ST表是基于倍增法的算法。对倍增法不熟悉的读者,可以先跳转阅读之前的文章:
算法探秘——与“二分法”相反的算法:倍增法
ST表的建造与查询
1、建造ST表
根据上文分析,在使用ST表查询时,需要先建造和预处理ST表的数据。
结合倍增法的思想,我们定义st[i][j]为从第i个元素开始,长度为2^j的区间最小值(以下都以最小值为例)。
而在前面已经分析到了,长度为2^j的区间最小值,就是两个长度为2^(j-1)的区间最小值中的较小值。
其中,第一个长度2^(j-1)区间的起点为i,第二个长度2^(j-1)区间的起点为i+2^(j-1),因此递推关系式可以写出:
st[i][j]=min(st[i][j-1], st[i+(1<<(j-1))][j-1])(注意,1<<(j-1)是2^(j-1)的位运算表示)
既然是递推关系,那就要思考边界条件。当j=0时,区间长度为1,区间的最值就是当前项的值。因此st数组的初始化代码可以这么编写:
for(int i=1;i<=n;i++){
st[i][0]=a[i];
}
接下来就是使用递推公式计算ST表各值了。这里有个小细节要思考:到底最长区间长度为2的几次方呢?
设最长长度是2的p次方,数组长度为n,需要满足下面的关系式:
使用数学中的log函数转换一下:
此时满足条件的最大整数p就对应着最长区间长度2^p。在C++的<cmath>库中有现成的log2(n)函数,可以帮助计算p值:
int p=log2(n); //int自动取整
for(int j=1;i<=p;j++){ //先枚举区间长度,从小到大
for(int i=1;i+(1<<k)-1<=n;i++){ //再枚举区间的左边界,要注意区间不能越界
st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
}
循环需要进行
趟,每趟需要计算小于n次(第一趟为n-1次,第二趟为n-3次,第三趟为n-7次,以此类推),因此
总时间复杂度为O(nlogn),并且计算量要小于nlogn。
上述寻找递推关系、边界条件的过程,其实也是在寻找最优子结构的过程,并且递推公式“无后效性”,因此ST表的递推关系其实是动态规划的过程。
动态规划是算法竞赛考察的“常客”,考验选手的问题分析与代码实现能力,会在之后单独开辟一个合集来详细介绍,敬请期待~
2、使用ST表查询
想要查询区间[L,R]的最小值,同样要先做一些数学分析。
区间的长度为len=R-L+1,由于len不一定是2的幂次方数,所以我们用两个小区间覆盖它时,需要有重复覆盖的部分。
设小区间的长度为x,需要x≤len且2*x≥len的区间长度能保证覆盖。例如len=21时,x=16的两个小区间就能覆盖。
因此,覆盖长度为len的区间,所需的2个小区间的长度为
。
最后注意,由于两个小区间会有重复覆盖的部分,因此右区间的起点不是左区间终点,而是由右区间的终点往前推,即起点为R-(1<<x)+1。
最终查询对应的代码如下:
int L,R;
cin>>L>>R;
int x=log2(R-L+1);
int ans=min(st[L][x],st[R-(1<<x)+1][x]);
接下来结合2道例题,带读者们去使用代码实现ST表的应用。
例题1:
ST 表 && RMQ 问题
算法解析
1、对于前30%的数据,我们先来试试看暴力求解,即题目给出一个区间,我们就遍历区间找出里面的最大值。时间复杂度为O(mn)。
参考代码:
#include<iostream>
using namespace std;
int a[100005];
int n,m;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
while(m--){
int l,r;
cin>>l>>r;
int ans=0;//题中保证数据都是非负数,因此极小值可以设为0
for(int i=l;i<=r;i++){
ans=max(ans,a[i]);
}
cout<<ans<<endl;
}
return 0;
}
评测结果:
2、后70%的数据,就要使用ST表来查询。由于前文已经非常详细地介绍了ST表的处理与查询过程,这里还请读者们阅读并理解完整版代码。
参考代码:
#include<iostream>
#include<cmath>
using namespace std;
int a[100005];
int st[100005][20]; //区间长度最多为100000,小于2的17次方
int n,m;
int l,r;
int ans;
int main(){
ios::sync_with_stdio(0); //加快cin、cout速度
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
st[i][0]=a[i]; //输入的同时初始化st表边界情况
}
//下面是预处理st表(题目是求区间最大值)
int p=log2(n);
for(int j=1;j<=p;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);//注意加减的运算优先级大于左移运算
}
}
//下面是使用st表进行m次查询
while(m--){
cin>>l>>r;
int x=log2(r-l+1); //计算小区间长度
ans=max(st[l][x],st[r-(1<<x)+1][x]);
cout<<ans<<endl;
}
return 0;
}
评测结果:
这时候有读者要问了,明明已经用ST表来查询了,怎么还能超时呢?代码中还有优化的地方吗?还真有。
(1)将“endl”替换成’\n’。由于endl除了换行外还会清空缓冲区,因此速度比’\n’会慢很多;
(2)根据题干提示,使用快读的模版,会比cin的效率更高。关于快读的知识,在很早期的文章中就有介绍:
信息学奥赛的编程技巧(2)——提高输入速度
例题2:
忠诚
算法解析
将题干解读一下,发现要求的就是给定多个区间,求每个区间里的最小值,是一个经典的RMQ问题。
事实上,解决这类问题的算法不仅仅有ST表,还有线段树、树状数组、分块等多种做法。
在这些算法中,ST表所占的优势是算法和编码简单,并且查询的时间复杂度为O(1),而线段树、树状数组查询的时间复杂度为O(logn)。
但是ST表要求数据是静态的,如果数组需要频繁维护,那么ST表就需要频繁更新,每次更新的时间复杂度都为O(nlogn),就没有更新维护时间复杂度为O(logn)的线段树和树状数组快了。
回到本题,直接修改上一题的代码即可。这里加入了手动递推log2函数的各项整数值,读者们可以关注一下。由于<cmath>库中计算log2函数的结果是浮点数,因此会比自己递推计算log2整数要稍慢一点。
代码展示:
#include<iostream>
#include<cmath>
using namespace std;
int st[100005][20]; //区间长度最多为100000,小于2的17次方
int lg2[100005]; //手动计算各log2函数的值
int m,n; //m笔帐,n个问题
int l,r;
int ans;
int main(){
ios::sync_with_stdio(0);
cin>>m>>n;
lg2[1]=0;
for(int i=2;i<=m;i++){
lg2[i]=lg2[i>>1]+1; //基于对数函数性质的递推
}
for(int i=1;i<=m;i++){
cin>>st[i][0]; //输入的同时初始化st表边界情况
}
//下面是预处理st表,求区间最小值
int p=lg2[m];
for(int j=1;j<=p;j++){
for(int i=1;i+(1<<j)-1<=m;i++){
st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
}
//下面是使用st表进行n次查询
while(n--){
cin>>l>>r;
int x=lg2[r-l+1]; //计算小区间长度
ans=min(st[l][x],st[r-(1<<x)+1][x]);
cout<<ans<<" ";
}
return 0;
}
总结
至此,奥赛大纲中数据结构的线性结构内容除了NOI级的“块状链表”外,入门级和提高级的知识点就全部介绍完啦~
相关推荐
- 重生之我在头条学html网页编程,这一世我一定学好,成为编程高手
-
有人要问了html是什么东西?就是用来设计网页的一种语言会不会很难啊?这是很多朋友担心的,我告诉大家这是最简单最基础也最容易学习的一款入门级语言,当初我也是经常因为学不会C语言而苦恼自从学习了html...
- 如何在网页3D CAD中创建一个三维管道模型
-
前言在网页CAD中进行三维建模是一项有趣的任务。本文将介绍如何利用mxcad3d来创建三维管道模型。该工具提供了一系列三维建模功能的API,使得建立复杂的管道结构变得简单直观。安装在此之前,需要先安装...
- 网页模版如何用
-
网页模版已成为如今网站建设的核心工具。随着互联网需求的增长,越来越多的企业和组织需要建立自己的网站,以展示他们的品牌和服务。在这个过程中,网页模版为他们提供了一种简单而高效的方式来构建网站。所谓网页模...
- AI嵌入式Flowcode编程网页开发人员入门指南
-
WebDeveloper允许使用FlowcodeIDE环境开发具有交互性的网页。可以在2D面板中添加特殊网页组件,以创建网页的视觉表示,并可以使用流程图添加交互功能。它的引入意味着Flowcod...
- 用Deepseek制作网页版的汉诺塔游戏保姆级教程
-
在deepseek中输入:“帮我做一个网页版的汉诺塔演示游戏,游戏包含2层、3层、4层、5层的汉诺塔游戏演示,制作自动求解演示按钮,点击按钮就可以生成出步数,同时自动演示最优解动画。”最后把生成的程序...
- TaskBuilder前端页面CSS样式规则设置
-
在前端页面设计器内,点击底部的“CSS样式”选项卡,可以打开CSS样式设计器,在此查看和设计当前页面的CSS样式规则,如下图所示:3.3.6.1引入外部样式文件如果要在页面中引入外部CSS文件,可以点...
- 使用 Python、FastHTML 和 Uvicorn 构建简单的博客网站
-
FastHTML是2024年7月推出的PythonWeb框架,是一个简单但功能强大的框架,允许开发人员使用纯Python构建Web应用程序。(不需要复杂的模板引擎)。Fast...
- 用AI可以生成HTML网页了,很多初级前端都要失业了
-
即使你完全不懂html,javascript,css,也能做出漂亮的网页,这在以前是不可想象的,而现在确是可行的,因为有这样一个项目:openUI。openUI不仅仅能生成html页面,还能生成自适应...
- python原始套接字socket下载http网页文件到txt
-
python原始套接字socket下载http网页文件到txtimportsocketdefdownload_webpage(url,output_file):try:...
- 高效排版:实现DeepSeek生成内容Word格式排版并导...
-
高效排版:实现DeepSeek生成内容Word格式排版并导出的经典方法,步骤简洁高效:DeepSeek生成内容复制出来容易出现乱码,下面介绍一种比较高效简单的方法!一、核心三步法1.调整模型模式在D...
- 打工人福音!3分钟教你学会word精美排版
-
昨天大熊介绍了word一键排版的三种办法,今天我们来详细讲讲第二种办法,用html代码实现一键排版,然后再导出pdf实现精美效果。打工人,打工魂,你是不是也有以下烦恼?下面是我经过多次和Deepsee...
- 使用 HTML 创建可折叠的交互式组件,一行 JS 代...
-
如果你想创建一个可折叠的交互式组件,使用<details>元素即可,一行JavaScript也不用写。<details>组件定义了一个可折叠的容器,它的第一个元素必须...
- 新手小白1分钟学会Word——文档的编辑1.1
-
天空一声巨响,迷人的我闪亮登场,亲爱的家人们,周末好呀!话不多说,咱们继续开干!昨天说到本节还有个小尾巴,那咱们就把这个小尾巴了结了,然后开始新篇章~四、保存文档我们对文档编辑完之后最重要的一步就...
- 超强!DeepSeek+HTML制作数据看板,老板看了都点赞
-
DeepSeek以极强的推理能力,支持生成各种代码,比如Python、SQL、Matlab、JS、HTML等,你可以拿这些代码放到编译器里,就能直接跑出结果,比如机器学习算法、exe应用、可视化图表、...
- 什么是Tailwind CSS
-
什么是TailwindCSSTailwindCSS是一个实用优先(Utility-First)的CSS框架,其核心思想是通过直接在HTML中组合预定义的类名来快速构建界面样式,无需编写传...
- 一周热门
- 最近发表
- 标签列表
-
- filter函数js (37)
- filter函数excel用不了 (73)
- 商城开发 (40)
- 影视网站免费源码最新版 (57)
- 影视资源api接口 (46)
- 网站留言板代码大全 (56)
- java版软件下载 (52)
- java教材电子课本下载 (48)
- java技术的电子书去哪看 (33)
- 0基础编程从什么开始学 (50)
- java是用来干嘛的 (51)
- it入门应该学什么 (55)
- java线上课程 (55)
- 学java的软件叫什么软件 (38)
- 程序开发软件有哪些 (53)
- 软件培训 (59)
- 机器人编程代码大全 (50)
- 少儿编程教程免费 (45)
- 新代系统编程教学 (61)
- 共创世界编程网站 (38)
- 最容易入门的编程语言 (33)
- 亲测源码 (36)
- tan sin cos 图 (33)
- 三角函数积分公式表 (35)
- 函数的表示方法 (34)