C++算法进阶系列之倍增算法 ST 表

1. 引言

前文使用倍增算法实现了快速求幂的运算,本文继续讲解ST表,ST表即倍增表,本质就是动态规划表,记忆化了不同子问题域中的结果,用于实时查询。只是动态规划过程和传统的稍有点不一样,采用了倍增思想。ST表往往用于存储子区间信息,如某区间的最值……

是不是所有的区间问题都可以使用ST表?

某个区间查询问题是否适用ST表,在于其进行的操作是否允许区间重叠。如下图所示:

4.png

如求 [1,6]区间的最大值,可以使用如下 2 种方案:

  • 直接在[1,6]子区间内使用求最值算法,找出最值。这个算法较易实现,一个循坏语句就搞定。

5.png

  • 如果已经知道了区间[1,5]和区间[3,6]的最大值,这两个区间可认为是区间[1,6]的子区间,且两者有重叠部分,如图可知区间[1,6]最大值是两个子区间中的较大值,即为 10。类似于这样的区间关系,是可以使用 ST表实现的。

6.png

为什么称ST表为倍增表?

整个数组是一个区间,则分割成小区间的方案可以是:

  • 长度为 1 的区间:[0,0],[1,1],[2,2]……
  • 长度为 2 的区间:[0,1],[1,2],[2,3]……
  • 长度为 3 的区间:[0,2],[1,3],[2,4]……
  • 长度为 4 的区间:[0,3],[1,4],[2,5]……
  • 长度为 5 的区间……

还可以划分出长度为6、7、8……的区间,如此分肯定是可行的,但是显得过于零碎、过多,维护代价较高。倍增法分割的原则是按长度为 1、2、4、8、16……分割,也就是按 2 的幂次方分割,这便是倍增表的由来。

  • 长度为 1 的区间:[0,0],[1,1],[2,2]……
  • 长度为 2 的区间:[0,1],[1,2],[2,3]……
  • 长度为 4 的区间:[0,3],[1,4],[2,5]……
  • 长度为8 的区间:[0,7],[1,8],[2,9]……

为什么说这种方案是可行的?

根据前面的分析可知,如果知道当前区间的子区间的最大值且子区间之间连续或重叠的,则当前区间的值可由子区间推导出来。从倍数关系来看,长度为 8 的区间可分割成 2 个长度为 4 的区间,长度为 4 的区间可分割成 2 个长度为 2 的区间……

如下图所示,任何一个区间都可以分割成两个与之有关联的子区间,从而减少更多细碎区间的存在。

7.png

2. 生成 ST 表

区间应该有 3 个属性,左端点(left)、右端点(right)、此区间的最大值。以下分别分析长度不同时区间的左端与右端的关系。

先以长度为 8 的区间[0,7]为例探讨左端与右端的关系:

  • left=0,right=left+23-1=0+8-1=7

以长度为 4 的区间[4,7]为例探讨左端与右端的关系:

  • left=4,right=left+22-1=4+4-1=7

以长度为 2 的区间[4,5]为例探讨左端与右端的关系:

  • left=4,right=left+21-1=4+2-1=5

以长度为 1 的区间[4,4]为例探讨左端与右端的关系:

  • left=4,right=left+01-1=4+1-1=4

再观察求幂运算中指数的通用性:区间长度为 1 时指数为 0;区间长度为 2 时指数为 1;区间长度为 4 时指数为 2;区间长度为 8 时指数为 3……也就是如果知道了left以及区间长度则可计算出右区间的大小,右区间是动态值。

9.png

对于长度为 10的数列而言,到底能划分成多少个不等的区间?

从上述例子来看,数组长度为 10 时,区间最大长度为 8也就是 23 就可以了,其值是 log10(以 2 为底数) 向下取整。有了上述推导的支撑,如可创建 ST就应该呼之欲出了。

创建一个二维数组,命名为 ST表。行坐标表示左端,列坐标并不直接表示右端,而是表示指数,有了指数信息即可以表示区间长度,又能计算出右端大小。如下图 st[0][0]表示的left=0、right=left+20-1=0,即区间[0,0]

10.png

数组存储对应区间的最大值,根据上述的推论可知,区间长度为1的最大值为自己。

11.png

right=1即表示长度为 2 的区间,此时,如下图 st[0][1]的值应该如何推导出来。

12.png

回到上文中的推导理论,知道区间长度为 2 的最大值,可以是它的 2 个长度为 1 的子区间的中的最大值。如下图所示,区间[0,1]的最大值是 max([0,0],[1,1] )的最大值,即为 8

13.png

问题是如何写出动态转移公式?

再分析一下:

  • 求指数为 1的区间值,得从指数为 0的区间找,首先,指数要降级。
  • 左边子区间[0,0]left值和区间[0,1]的左值相同,右边子区间[1,1]的左端值是左子区间的left+20=0+1=1

如此,动态转移公式也就可以出来了:

  • right=0时,st[left][right]=nums[left]
  • right!=0时,st[left][right]=max(st[left][right-1],st[left+2right-1][right-1]

编码实现:

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
//st 表
int st[100][10];
//原数组
int nums[100];
//实际长度
int n,col;
/*
*  初始化
*/
void init() {for(int i=0; i<n; i++) {scanf("%d",&nums[i]);st[i][0]=nums[i];}
}
/*
* 创建 ST 表
*/
void setSt() {col =(int)(log(n)/log(2));for(int j=1; j<=col; j++)for (int i=0; i<n-(1<<j)+1; i++)st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
/*
*输出ST表
*/
void show() {for(int i=0; i<n; i++) {for(int j=0; j<=col; j++) {cout<<st[i][j]<<"\t";}cout<<endl;}
}
int main() {scanf("%d",&n);init();setSt();show();return 0;
}

3. 查询

st表的创建,目的是快速查询某区间的最大值,下面讨论如何准确定位区间,以及找到最大值。

如下图所示,如果用户输入的区间是[0,3],则运气很好,因为恰好有这么一个区间。那么如何得到 st[i][j]i和j的值?

显然i=0,求 j的表达式为:i+2j-1=3,则 2j=4-0=4;j=log4(以 2 为底); j=2,也就直接从 st[0][2]得到最大值。

9.png

但是,如果输入的区间是[0,6],不存在这么一个区间,则需要从能作为此区间的子区间中查找。

假设在st[i][j]中,i=l,j=p,len=r-l+1(区间长度),找最大的p应满足2p <= len(以l为起点的st表数据覆盖[l,r]中数足够多),则p=int(log(len)/log(2))

左子区间为 st[l][p],右子区间为 st[x][r]。如何求x,对于st[x][r],应有:[x][x+pow(2,p)-1]<=>[x][r]。所以, x+pow(2,p)-1=r,移项,得:x=r+1-pow(2,p),显然,x>=l ( l+pow(2,p)-1<=r,x+pow(2,p)-1=r,故x>=l)

综上[l,r]的最大值 = max( f[l][p] , f[x][R] )

完整代码:

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
//st 表
int st[100][10];
//原数组
int nums[100];
//实际长度
int n,m,col;
/*
*  初始化
*/
void init() {for(int i=0; i<n; i++) {scanf("%d",&nums[i]);st[i][0]=nums[i];}
}
/*
* 创建 ST 表
*/
void setSt() {double a= log(5);cout<<a<<endl;col =(int)(log(n)/log(2));for(int j=1; j<=col; j++)for (int i=0; i<n-(1<<j)+1; i++)st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
/*
*输出ST表
*/
void show() {for(int i=0; i<n; i++) {for(int j=0; j<=col; j++) {cout<<st[i][j]<<"\t";}cout<<endl;}
}
int find() {int l,r,p,x;for(int i=1; i<=m; i++) {scanf("%d%d",&l,&r);p=(int)(log(r-l+1)/log(2));x=r-(1<<p)+1;int res= max( st[l][p], st[x][p]);cout<<res<<endl;}
}
int main() {scanf("%d%d",&n,&m);init();setSt();show();find();return 0;
}

测试结果:

15.png

4. 总结

区间查询有很多方式,ST是不错的选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/133313.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

h5开发网站-页面内容不够高时,如何定位footer始终位于页面的最底部

一、问题描述&#xff1a; 在使用h5开发页面时&#xff0c;会遇到这个情况&#xff1a;当整个页面高度不足以占满显示屏一屏&#xff0c;页脚不是在页面最底部&#xff0c;影响用户视觉。想让页脚始终在页面最底部&#xff0c;我们可能会想到用&#xff1a; 1.min-height来控…

初识Mybatis(二)动态SQL、缓存和逆向工程

动态SQL Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能&#xff0c;它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。 &#xff08;比如多条件查询的设置&#xff0c;实现判空等&#xff09; 1、if 创建DynamicSQLMapper接口&#xff0c;DynamicSQL…

小程序中如何查看指定会员的付款记录

在小程序中&#xff0c;我们可以通过一些简单的步骤来查看指定会员的付款记录。下面是具体的操作流程&#xff1a; 1. 找到指定的会员卡。在管理员后台->会员管理处&#xff0c;找到需要查看付款记录的会员卡。也支持对会员卡按卡号、手机号和等级进行搜索。 2. 查看会员卡…

springboot3 + java虚拟线程初体验

java虚拟线程介绍 虚拟线程是 Java 19 的 预览特性&#xff0c;估计会在Java 21被纳入 JDK 的正式版本中&#xff0c;会在2023年9月发布&#xff0c;目前springboot 3 已经提供了对虚拟线程的支持。 虚拟线程和平台线程主要区别在于&#xff0c;虚拟线程在运行周期内不依赖操…

竞赛选题 基于机器视觉的火车票识别系统

文章目录 0 前言1 课题意义课题难点&#xff1a; 2 实现方法2.1 图像预处理2.2 字符分割2.3 字符识别部分实现代码 3 实现效果最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于机器视觉的火车票识别系统 该项目较为新颖&#xff0c;适合作为竞赛…

记录第一个启动代码的诞生

核使用R52&#xff0c;参考汇编模板&#xff0c;一步一步来实现。 首先是ld文件&#xff0c;这个没啥好说的&#xff0c;主要是关注给vector_table划一块地址、stack地址&#xff0c;如下&#xff1a; .text.intvec :{_vectors_start .;KEEP(*(.text.intvec))_vectors_end .;…

vscode 当中vue 全局自定义组件没有提示以及一些技巧

阅读技术文章可以查漏补缺&#xff0c;借鉴别人编码方式提高代码水平 阅读优秀项目 可以扩展业务处理能力 坚持每天阅读&#xff0c;每天学习新东西 积少成多&#xff0c;水到渠成 在写项目时候&#xff0c;我全局注册了组件&#xff0c;YhSwitch&#xff0c;但是在使用时候&am…

数值类型表示二——定点和浮点格式

目录 目录 定点小数与定点整数 定点小数原反补的转换 定点小数与定点整数的取值范围 位数扩展的区别 浮点数的格式 浮点数的规格化 规格化处理举例 例1&#xff1a; 例2&#xff1a; 特例&#xff1a; 知识点总结&#xff1a; 浮点数的IEEE754标准 移码的回顾&…

免费,开源,可批量的离线图片文字提取软件OCR

Umi-OCR 文字识别工具 免费&#xff0c;开源&#xff0c;可批量的离线OCR软件 适用于 Windows7 x64 及以上 免费&#xff1a;本项目所有代码开源&#xff0c;完全免费。方便&#xff1a;解压即用&#xff0c;离线运行&#xff0c;无需网络。批量&#xff1a;可批量导入处理图片…

企业可以自己建立大数据平台吗?有哪些好处?

随着企业的快速发展&#xff0c;企业累积了越来越多的数据&#xff0c;但管理巨量的大数据是一件非常难的事情&#xff0c;且很多数据没有充分发挥作用。因此不少企业在问&#xff0c;企业可以自己建立大数据平台吗&#xff1f;有哪些好处&#xff1f; 企业可以自己建立大数据…

sql存储引擎

-- 查询建表语句 --可以查看引擎 show create table account; -- 可以看到默认引擎 InnoDB ENGINEInnoDB -- 查看当前数据库支持得存储引擎 show engines ; # InnoDB 默认 存储引擎 # MyISAM sql早期默认 存储引擎 # MEMORY 存储在内存中 用来做临时表和缓存 存储引擎 …

Oracle,高斯创建自增序列

某些时候,需要获取到一个自增值 然后点击左下 Apply 也可以通过SQL语句执行 dual在Oracle中是张虚拟表&#xff0c;通常用于执行这样的查询 Oracle中查询语句: select 序列名.nextval from dual 在高斯数据库中:查询是 select my_sequence.nextval 不需要加form xxx 也…

文心一言插件开发全流程,ERNIE-Bot-SDK可以调用文心一言的能力

文心一言插件开发 前言插件插件是什么工作原理申请开发权限 开始第一步&#xff1a;安装python第二步&#xff1a;搭建项目manifest 描述文件&#xff1a;ai-plugin.json插件服务描述文件&#xff1a;openapi.yaml开发自己的plugin-server 第三步&#xff1a;上传插件 SDK相关链…

QT子线程或自定义类操作访问主界面UI控件的几种方法

前言 QT创建窗体工程&#xff0c;一般在MainWindow或Dialog类里可以直接通过ui指针访问控件&#xff0c;但是添加新的类后又如何访问呢&#xff0c;可以通过以下几种方式&#xff1a; 将ui指针公开后直接访问 &#xff08;1&#xff09;例如有个自己定义的类CustomCl…

Java基础篇

目录 1、Java语言有哪些特点 2、面向对象和面向过程的区别 3、八种基本数据类型的大小 4、标识符命名规则 5、Java 关键字 6、访问控制 7、instanceof 关键字的作用 8、final 关键字的作用 9、static 关键字作用 10、transient 关键字的作用 11、try catch final…

国家网络安全周 | 保障智能网联汽车产业,护航汽车数据安全

9月13日上午&#xff0c;2023年国家网络安全宣传周汽车数据安全分论坛在福州海峡国际会展中心正式举办。本次分论坛主题是“护航汽车数据安全&#xff0c;共促产业健康发展”&#xff0c;聚焦汽车数据安全、个人信息保护、密码安全、车联网安全保险等主题。 与此同时&#xff…

Golang代码漏洞扫描工具介绍——govulncheck

Golang Golang作为一款近年来最火热的服务端语言之一&#xff0c;深受广大程序员的喜爱&#xff0c;笔者最近也在用&#xff0c;特别是高并发的场景下&#xff0c;golang易用性的优势十分明显&#xff0c;但笔者这次想要介绍的并不是golang本身&#xff0c;而且golang代码的漏洞…

重数和众数问题——C语言实现

题还是很简单的&#xff0c;理清思路就可以了♪(&#xff65;ω&#xff65;)&#xff89; 问题描述&#xff1a; 给定含有n个元素的多重集合S&#xff0c;每个元素在S中出现的次数称为该元素的重数。多重集S中重数最大的元素称为众数。 例如&#xff0c;S{1&#xff0c;2&…

基于ntchat的微信群聊同步机器人

微信群有500人上限的限制&#xff0c;建立多个群的话又有信息无法互通的不便&#xff0c;此机器人通过自动将消息转发到同一个同步组内的所有群&#xff0c;消除这一不便性&#xff0c;间接达成扩大群成员数的目的。 效果演示&#xff1a; 项目地址&#xff1a; https://gith…

Python综合案例(数据计算)

filter算子 接受一个函数&#xff0c;可用lambda快速编写&#xff1b;函数对RDD 数据逐个处理&#xff0c;得到True的保留到返回值的RDD中 """ filter成员方法的使用 """ from pyspark import SparkConf, SparkContext import os os.environ[P…