利用ST表解决RMQ问题

ST表 – 静态查询区间最值问题

一.与线段树比的优缺点

线段树可以O(n)的时间建树,O(logn)的时间复杂度情况下查询区间最值,但是ST表利用空间换时间,可以在O(nlogn)的时间打表,O(1)的时间复杂度下静态查询区间最值。

当查询次数多且静态查询的情况下,选择ST表来做区间最值查询更合适。

二.ST表的具体实现过程

1.预处理

预处理也是一个利用动态规划打表的过程。
①定状态:a[ i ] : 初始数组;f[ i ][ j ] :第 i 个数起连续的 2j 个数中的最大值;
②初始化:f[ i ][ 0 ] = a[ i ],即第 i 个数起,长度为 20的最大值为a[ i ]本身;
③状态转移方程:因为我们每次能把[i,j]分成两段 – [i, i + 2j-1-1] 与 [i + 2j-1,i + 2j-1]
所以其状态转移方程为f[i][j] = max(f[i][j-1], f[i+(1<<(j-1))][j-1]);

例如:3 5 4 7 6 2 3
f [ 1 ][ 1 ] = max(3,5) = 5;
f[ 1 ][ 2 ] = max(3 5 4 7) = 7;

代码:

//初始化 
void init(int n)
{
	for(int i = 1; i <= n; i++)
		f[i][0] = a[i];
		
	for(int j = 1; (1<<j) <= n; j++)
		for(int i = 1; i + (1<<j) - 1 <= n; i++)
			f[i][j] = max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}

2.查询

假如说查询区间[ l , r ];
根据max的性质,可以把[l,r]拆分成两个相重叠的区间(例如:查询2 3 4 5 6,就可以分成2 3 4 5和3 4 5 6这两个区间),每次找到覆盖这个闭区间的最小幂(左边界取 l,右边界取 r),区间长度len = r - l + 1,所以k = 2log(len)(即左右子区间的长度,且log(len)向下取整)

–> query(l,r) = max(f[ l ][ k ], f[ r - 2k + 1 ][ k ])

代码:

//查询区间l ~ r的最值(目前写的是最大值,最小值改为 min) 
int query(int l,int r)
{
	int k = 0;
	while((1<<(k+1)) <= r - l + 1)
		k++;
	return max(f[l][k],f[r - (1<<k) + 1][k]);
}

三.模板

//ST表求RMQ问题 

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 5e4 + 10;
const int INF = 0x3f3f3f3f;
const int N = 1e2 + 10;

int n,q,a[maxn],f[maxn][N];		//a[] -- 初始数值     f[][] -- 区间最值 

//初始化 
void init(int n)
{
	for(int i = 1; i <= n; i++)
		f[i][0] = a[i];
		
	for(int j = 1; (1<<j) <= n; j++)
		for(int i = 1; i + (1<<j) - 1 <= n; i++)
			f[i][j] = max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}

//查询区间l ~ r的最值(目前写的是最大值,最小值改为 min) 
int query(int l,int r)
{
	int k = 0;
	while((1<<(k+1)) <= r - l + 1)
		k++;
	return max(f[l][k],f[r - (1<<k) + 1][k]);
}

int main()
{
	//n个初始值,q次询问区间最值 
	scanf("%d%d",&n,&q);
	for(int i = 1; i <= n; i++)
		scanf("%d",&a[i]);
	init(n);
	
	for(int i = 1; i <= q; i++)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		
		printf("%d\n",query(l,r));
	}
	return 0;
}

四.模板题

洛谷 P3865 【模板】ST表
其实就相当于模板题,只是因为题目特殊,时间只能到0.9秒,像我这样写的ST表第11个点会超时,所以加上了快读。

#include <stdio.h>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <queue> 
#include <stack>
#include <cstring>
#include <iostream>
#include <algorithm>
#include<functional>

using namespace std;

typedef long long ll;
const int maxn = 1e5 + 10;
const int INF = 0x3f3f3f3f;

int n,m,a[maxn],f[maxn][50];

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

void init()
{
	for(int i = 1; i <= n; i++)
		f[i][0] = a[i];
		
	for(int j = 1; (1<<j) <= n; j++)
		for(int i = 1; i + (1<<j) - 1 <= n; i++)
			f[i][j] = max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}

int query(int l, int r)
{
	int k = 0;
	
	while((1 << (k+1)) <= r - l + 1)
		k++;
	
	return max(f[l][k],f[r-(1<<k)+1][k]);
}

int main()
{
	n = read(), m = read();
	
	for(int i = 1; i <= n; i++)
		a[i] = read();
		
	init();
	
	for(int i = 1; i <= m; i++)
	{
		int l,r;
		l = read(), r = read();
		
		printf("%d\n",query(l,r));
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值