st表

摘要

ST表是一个用来处理区间最值查询(Range Maximum/Minimum Query)的离线算法。该算法分为离线预处理O(n log n)和查询O(1)两个部分,其用到了倍增的思想。
某个区间查询问题是否适用ST表,关键在于其进行的操作是否允许区间重叠,例如max(a,b,c) = max{max(a,b),max(b,c)}就可以用ST表维护,而区间和问题则不能维护。

问题描述

给定一个长度为n的序列,有m次询问,每次给定区间[L , R],求区间内最大值。

算法思路

定义st表
我们设 st[i][j] 为从 i 开始的 2^j 个数中的最大值。假设这n个数存放的序列a中,根据定义 st[i][j] = max{a[k] | i <= k <= i+2^j - 1}。
查询操作
假设我们已经更新完st表(下文会讲如何在O(n log n)时间内更新),那么我们应该如何利用上述定义的st表查询[L , R]内的最大值呢?
根据我们上述st表的定义,我们发现,我们能在st表中查询的区间(假设为[a,b])都只能从某个位置a开始向后 2 k − 1 2^k-1 2k1个区间(k是自然数),所以如果a-b 并不恰好等于 2 k 2^k 2k,那么多出来的部分(即 [ a + 2 k , b ] [a+2^k , b] [a+2k,b])我们并不能直接求出来,只能用类似的方法求出 [ b − 2 k + 1 , b ] [b-2^k+1,b] [b2k+1,b]的最大值,那么最终[a,b]的最大值不就是以上两段区间的max吗?当然这里的k要取使得 a + 2 k &lt; b a+2^k &lt; b a+2k<b成立的最大值,即 l o g 2 b − a + 1 log_2^{b-a+1} log2ba+1
举个例子,求[2 , 10]的最大值可以拆分为求[2 , 9]的最大值和[3 , 10]的最大值。
于是查询操作(查询[L , R] )就顺利成章的是max(st[L][k] , st[R-(1<<k) + 1][k])。
预处理
我们在上述证明了查询操作确实是O(1)的,所以如果我们真的能按照st表定义的那样去更新它,那么该算法就真正的完成了。更新st表用到了动态规划的思想。
更新中仍然用到了倍增的思想,使得更新操作从O(n^2)优化到了O(n log n),初始状态st[x][0] = a[x],st[x][j] = max(st[x][j-1] , st[x + (1<<(j-1)) ][j-1])。其思想和上述重叠查询类似,不再赘述。但是要注意更新顺序,因为其中 j (第二维)才是阶段,而第一维 x 是状态,所以对于 j 的循环要放在最外层。

代码示例

#include<cstdio>
#include<cmath>
#include<iostream>
using namespace std;
int n,m;
const int N = 1e5+10;
int a[N],st[N][25],Log[N];//2^20就过一百万了,完全够用 
//初始化st表 
void init(){
	Log[1] = 0;//预处理log函数 
	for(int i = 2;i <= n+1;i++) Log[i] = Log[i/2]+1;
	for(int i = 1;i <= n;i++) st[i][0] = a[i];
	for(int j = 1; (1<<j) <= n;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]);
		}
	}
}
int ask(int l,int r){
	int k = Log[r-l+1];
	int mx = max(st[l][k],st[r-(1<<k)+1][k]);
	//printf("%d %d\n",k,mx);
	return mx;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i++) scanf("%d",a+i);
	init();
	for(int i = 1,l,r;i <= m;i++){
		scanf("%d%d",&l,&r);
		printf("%d\n",ask(l,r));
	}
}
©️2020 CSDN 皮肤主题: 程序猿惹谁了 设计师:上身试试 返回首页