倍增(ST)算法

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

倍增(ST)算法
ST算法是求解静态空间的区间最大最小值问题(RMQ问题)的离线算法,类似于线段树和树状数组,其功能特性差不多,当实现起来的话,显然是ST算法更为简便。

它适用于静态空间的RMQ查询:给定长度为n的静态数列,做m次询问,每次给定L, R ≤ n,查询区间[L, R]内的最值。

ST算法的时间复杂度:预处理的是O(nlogn),查询的是O(1);
ST表的主体式一个二维数组dp[i][j],第一个状态表示需要需要查询区间的首元素,第二个状态表示从首元素开始向后延伸的长度。

在查询的时候,你输入的区间不一定总是2的倍数,那么查询的时候便会出现区间重复查询,但是这并不影响最后的结果,也不会影响时间复杂度。

例如:我输入的区间是(3,11),那么我计算出来的k = (int)(log(double(r - l + 1)) / log(2.0)),即k = 3。

那么我便会在区间(3,10)和(4,11)这两个区间中去最小值返回。

其中重复的区间是(4,10)。

倍增就是“成倍增长”,每次递增2倍。

倍增法的题目都利用了数的二进制表示(二进制划分):
N = a
0 2 0 + a
1
2 1 + a
2
2 2 + a
3
2 3 + a
4
2 4 + …
例如17,它的二进制是10001,第4和第0位是1,即 a
4 = a
= 1,把
这几位的权值相加,有 17 = 2 4 + 2 0 = 16+1。

数的二进制划分反映了一种快速增长的特性,第i位的权值 2i
等于前面所有权值的和加1:
2 i = 2 i − 1 + 2 i − 2 + … + 2 1 + 2 0 + 1
一个整数n,它的二进制表示只有logn位。

如果要从0增长到n,可以用1、2、4、…、 2 k为“跳板”,快速跳到n,这些跳板只有k = logn个。

倍增法的特点是需要提前计算出第1、2、4、…、 2 k
k个跳板,这要求数据是静态不变的,不是动态变化的。

如果数据发生了变化,所有跳板要重新计算,跳板就失去了意义。

下面都以最小值为例。

用暴力搜区间[L, R]的最小值,即逐一比较区间内的每个数,复杂度是O(n)的;m次查询,复杂度O(mn)。

暴力法的效率很低。

ST算法源于这样一个原理:一个大区间若能被两个小区间覆盖,那么大区间的最值等于两个小区间的最值。

例如下图中,大区间{4, 7, 9, 6, 3, 6, 4, 8, 7, 5}被两个小区间{4, 7, 9, 6, 3, 6, 4, 8}、{4, 8, 7, 5}覆盖,大区间的最小值3,等于两个小区间的最小值,min{3, 4}=3。

这个例子特意让两个小区间有部分重合,因为重合不影响结果。

图1.大区间被两个小区间覆盖
从以上原理得到ST算法的基本思路,包括两个步骤:
(1)把整个数列分为很多小区间,并提前计算出每个小区间的最值;
(2)对任意一个区间最值查询,找到覆盖它的两个小区间,用两个小区间的最值算出答案。

如何设计出这2个步骤的高效算法?
对于(1),简单的方法是把数列分为固定大小的小区间,即“分块”,它把数列分为,每块有个数,提前计算这个小区间的最值,复杂度是 O(n) 。

然后对于(2)的最值查询,每次计算量约为O(1)。

这种算法的效率比暴力法强很多,但是还不够好。

下面用“倍增”的方法来分块,它的效率非常高:(1)的复杂度是O(nlogn),(2)的复杂度是O(1)。

(1)把数列按倍增分成小区间
对数列的每个元素,把从它开始的数列分成长度为1、2、4、8、…的小区间。

下图给出了一个分区的例子,它按小区间的长度分成了很多组。

第1组是长度为1的小区间,有n个小区间,每个小区间有1个元素;
第2组是长度为2的小区间,有n个小区间,每个小区间有2个元素;
第3组是长度为4的小区间,有n个小区间,每个小区间有4个元素;

共有logn组。

图2.按倍增分为小区间
可以发现,每组的小区间的最值,可以从前一组递推而来。

例如第3组{4, 7, 9, 6}的最值,从第2组{4, 7}、{9, 6}的最值递推得到。

定义dp[s][k],表示左端点是s,区间长度为 2 k的区间最值。

递推关系是:
dp[s][k] = min{dp[s][k-1], dp[s + 1<<(k-1)][k-1]} 其中1<<(k-1)等于 2 k − 1。

用dp[][]来命名,是因为递推关系是一个动态规划的过程。

计算所有小区间的最值,即计算出所有的dp[][],复杂度是多少?图中的每一组都需计算n次,共logn组,总计算量是O(nlogn)。

(2)查询任意区间的最值
根据上面的分区方法,有以下结论:以任意元素为起点,有长度为1、2、4、…的小区;以任意元素为终点,它前面也有长度为1、2、4、…的小区。

根据这个结论,可以把需要查询的区间[L, R]分为2个小区间:以L为起点的小区间、以R为终点的小区间,让这两个小区间首尾相接覆盖[L, R],区间最值从两个小区间的最值求得。

一次查询的计算复杂度是O(1)。

区间[L, R]的长度是len = R-L+1。

两个小区间的长度是x,令x是比len小的最大2的倍数,有2*x ≥ len,这样保证能覆盖。

另外需要计算dp[][],根据dp[s][k]的定义,有 2 k = x。

例如len = 19,x = 16, 2 k= 16,k = 4。

已知len如何求k?计算公式是k = log2(len) = log(len)/log(2),向下取整。

以下两种代码都行:
int k=(int)(log(double(R-L+1)) / log(2.0)); //以10为底的库函数log()
int k=log2(R-L+1); //以2为底的库函数
log2()
如果觉得库函数log2()比较慢,可以自己提前算出LOG2,LOG2[i]的值与向下取整的log2(i)相等:
LOG2[0] = -1;
for(int i=1;i<=maxn;i++)
LOG2[i] = LOG2[i>>1]+1;
最后给出区间[L, R]最小值的计算公式,等于覆盖它的两个小区间的最小值:
min(dp[L][k],dp[R-(1<<k)+1][k]);
用这个公式做一次最值查询,计算复杂度是O(1)。

下面用一个模板题给出代码。

每天,农夫约翰的 n(1≤n≤5*104) 头牛总是按同一序列排队。

有一天, 约翰决定让一些牛们玩一场飞盘比赛。

他准备找一群在队列中位置连续的牛来进行比赛。

但是为了避免水平悬殊,牛的身高不应该相差太大。

约翰准备q(q(1≤q≤1.8*105) 个可能的牛的选择和所有牛的身高 hi(1≤hi≤106,1≤i≤n)。

他想知道每一组里面最高和最低的牛的身高差。

输入格式
第一行两个数 n,q。

接下来 n 行,每行一个数 h_i。

再接下来 q 行,每行两个整数 a 和 b,表示询问第 a 头牛到第 b 头牛里的最高和最低的牛的身高差。

输出格式
输出共 q 行,对于每一组询问,输出每一组中最高和最低的牛的身高差。

输入输出样例
输入
6 3
1
7
3
4
2
5
1 5
4 6
2 2
输出
6
3。

相关文档
最新文档