原创 首页 算法

RMQ 区间最值问题:ST 表

LinJHS 2

前言

RMQ (Range Minimum/Maximum Query) 问题是指:

对于长度为 n 的数列 A ,回答若干询问 RMQ(A,i,j) (i,j\le n) ,返回数列 A 中下标在 i , j 里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。

时间复杂度: O(N)~O(logN)

主要方法

  1. 朴素(即搜索), O(n)~O(qn) ,online。

  2. 线段树, O(n)~O(qlogn) ,online。

  3. ST (实质是动态规划), O(nlogn)~O(q) ,online。

  4. RMQ 标准算法:先规约成 LCA (Lowest Common Ancestor) ,再规约成约束 RMQ , O(n)-O(q) ,online。

ST 算法

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N=1e6+5,LogN=20;	//表示定义整型常量,1e6+5即为10^6+5

int log[N],f[N][LogN+5],a[N];
int n,m,x,y;

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
		scanf("%d",&a[i]);					//读入 n 个数据 
	log[0]=-1;								//log[0] 设为 -1 ,这样才能使 log[1] 为 0
	for(int i=1;i<=n;++i)
		f[i][0]=a[i],log[i]=log[i>>1]+1;	//预处理出长度为 1~n 的 log 值 
	for(int j=1;j<=LogN;++j)				//计算 f[i][j]
		for(int i=1;i+(1<<j)-1<=n;++i)		//注意区间边界不能超过 n
			f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
											//2j可以用右移运算符"<<"很快算出 
											//注意加减乘除运算符的优先级高于位移运算符 
	while(m--)
	{
		scanf("%d%d",&x,&y);
		int s=log[y-x+1];					//求log2(y-x-1)下取整的值 
		printf("%d\n",max(f[x][s],f[y-(1<<s)+1][s]));	//回答每个提问 
	}
	return 0;
}

一、简介

ST 算法 (Sparse Table) 。

以求最大值为例,设 f[i,j] 表示 [i,i+2^j-1] 这个区间内的最大值,那么在询问到 [a,b] 区间的最大值时答案就是 max(f[a,k], f[b-2^k+1,k]) ,其中 k 是满足 2^k\le b-a+1 (即长度)的最大的 k ,即 k=[\frac{\ln(b-a+1)}{\ln(2)}] 。

f[][] 的求法可以用动态规划, f[i,j]=max(f[i,j-1],f[i+2^(j-1),j-1]) 。

二、详细步骤

来看一下 ST 算法是怎么实现的(以最大值为例):

首先是预处理,用一个 DP 解决。设 a 是要求区间最值的数列, f[i,j] 表示从第 i 个数起连续 2^j 个数中的最大值。

例如数列 3 2 4 5 6 8 1 2 9 7

f[1,0] 表示第 1 个数起,长度为 2^0=1 的最大值,其实就是 3 这个数。

f[1,1]=3 , f[1,2]=5 , f[1,3]=8 , f[2,0]=2 , f[2,1]=4 ……

从这里可以看出 f[i,0] 其实就等于 a[i] 。这样, DP 的状态、初值都已经有了,剩下的就是状态转移方程。

我们把 f[i,j] (j\ge 1) 平均分成两段(因为 j\ge 1时, f[i,j] 一定是偶数个数字),从 i 到 i+2^{j-1}-1 为一段, i+2^{j-1} 到 i+2^j-1 为一段(长度都为 2^{j-1} )。

用上例说明,当 i=1 , j=3 时就是 3,2,4,5 和 6,8,1,2 这两段。 f[i,j] 就是这两段的最大值中的最大值。

于是我们得到了动规方程 f[i,j]=max(f[i,j-1],f[i+2^(j-1),j-1]) 。

接下来是得出最值,也许你想不到计算出 f 有什么用处,一般要想计算 max 还是要 O(logn) ,甚至 O(n) 。但有一个很好的办法,做到了 O(1) 。

还是分开来。如在上例中我们要求区间 [2,8] 的最大值,就要把它分成 [2,5] 和 [5,8] 两个区间,因为这两个区间的最大值我们可以直接由 f[2,2] 和 f[5,2] 得到。

扩展到一般情况,就是把区间 [l,r] 分成两个长度为 2^n 的区间(保证有 f 对应)。直接给出表达式:

    k=floor(ln(r-l+1)/ln(2));
    ans=max(f[l][k],f[r-2^k+1][k]);

这样就计算了从 l 开始,长度为 2^k 的区间和从 r-2^k+1 开始长度为 2^k 的区间的最大值,二者中的较大者就是整个区间 [l,r] 上的最大值。

三、算法流程

  1. 预处理

实质:动态规划
设 a[1...n] ,设 f[i][j] 表示从 a[i] 到 a[i+2^j-1] 最大值,共 2^j 个。
由于共 2^j 个,可将其分成两份,分别是 f[i][j-1] 和 f[i+2^(j-1)][j-1] 。
状态转移方程:
f[i][j]=max(f[i][j-1],f[i+2^(j-1)][j-1])
边界条件为 f[i][0]=a[i]
时间复杂度 O(nlogn)

  1. 询问

若我们询问区间 [l,r] 的最大值,则先求出最大的 x 满足 2^x\le r-l+1 ,那么区间 [l,r]=[l,l+2^x-1]\cup [r-2^x+1,r]
那么 [l,r] 的最大值为 max(f[l][x],f[r-2^x+1][x]),可在 O(1) 内算出。
虽然有交集,但并不影响,这就是 ST 算法只适用于求区间最值的原因。
对于求区间 [x,y]
K=log2(y-x+1)
ans=max(f[x][k],f[y-2^k+1][k])

  1. 技巧

因为 cmath 库中的 log2() 函数效率不高,所以除了调用 log2() 函数外,通常还会使用 O(N) 递推预处理出 1\ldots N 这 N 种区间长度各自对应的 k 值。具体地,设 log[d] 表示 \log_2(d) 向下取整,则 log[d]=log[d/2]+1 。


注:部分资料来源于百度百科

分享
如果这篇文章帮到你,可以请我喝一杯茶。

微信扫码打开

请我喝杯茶

如果这篇文章帮到你,可以请我喝一杯茶。

微信收款码
在商城请我一杯