【ST表】【倍增】RMQ问题

ST表

查询时间:O(1)
建表时间:O(nlogn)
注意: ST不支持在线修改
采用思想:倍增思想

F [ i ] [ j ] F[i][j] F[i][j]代表区间 [ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j1]的区间最大值为例

F [ i ] [ j ] F[i][j] F[i][j] :代表区间 [ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j1]的区间最大值,区间长度为 2 j 2^j 2j

递推求 F [ i ] [ j ] F[i][j] F[i][j]: 把区间长度为 2 j 2^j 2j的区间分成两个区间长度为 2 j − 1 2^{j-1} 2j1的区间

则递推公式: F [ i ] [ j ] = m a x ( 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][j]=max(F[i][j1],F[i+2j1][j1])

ST表创建:

n : 代表a[]数组长度
变量k: 满足 2 k ≤ n < 2 k + 1 2^k \leq n \lt2^{k+1} 2kn<2k+1, 2 k 2^k 2k代表最大区间长度,k需要尽可能的大,且要满足前面所说条件,故 k = l o g 2 n k = log_{2}n k=log2n(此时已经下取整了)

void init(int n)
{
	for(int i=1;i<=n;i++)
		f[i][0] = a[i];
	int k = log2(n);
	//循环方式类似 区间DP
	//判断也可以 : (1<<j) <= n
	for(int j=1;j<=k;j++)//循环k,区间长度从小到大
		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]);			
}

ST表查询:

查询 [ l , r ] [l,r] [l,r]区间最大值:

和上面一样,先计算k值, k = l o g 2 ( r − l + 1 ) k = log_2({r-l+1}) k=log2(rl+1)

区间 [ l , r ] [l,r] [l,r]最值 m x = m a x ( f [ l ] [ k ] , f [ r − ( 1 < < k ) + 1 ] [ k ] ) mx = max(f[l][k],f[r-(1<<k)+1][k]) mx=max(f[l][k],f[r(1<<k)+1][k]),取两个区间的最值,虽然两个区间可能有重合,但是不影响结果。

int query(int l,int r)
{
	int k = log2(r-l+1);
	//取两个区间的最值
	return max(f[l][k],f[r-(1<<k)+1][k]);
}

例题

题目大意:给定 n 个数,有 m 个询问,对于每个询问,你需要回答区间 [ l , r ] [l,r] [l,r] 中的最大值。

考虑暴力做法。每次都对区间 [ l , r ] [l,r] [l,r] 扫描一遍,求出最大值。

显然,这个算法会超时。
这里采用ST表思想:

二维数组 d p [ i ] [ j ] dp[i][j] dp[i][j]表示从第i位开始连续 2 j 2^j 2j 个数中的最小值

d p [ i ] [ j ] dp[i][j] dp[i][j] 的时候可以把它分成两部分,第一部分是从 [ i , i + 2 j − 1 − 1 ] [i,i+2^{j-1}-1] [i,i+2j11] ,第二部分从 [ i + 2 j − 1 , i + 2 j − 1 ] [i+2^{j-1},i+2^{j}-1] [i+2j1,i+2j1] ,为什么可以这么分呢?

其实我们都知道二进制数前一个数是后一个的两倍,那么可以把 [ i , i + 2 j − 1 ] [i , i+2^j-1] [i,i+2j1] 这个区间分成相等长度的两个区间, 那么转移方程很容易就写出来了。( d p [ i ] [ 0 ] dp[i][0] dp[i][0]就表示第i个数字本身)

模板

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int a[maxn];
int dp[200005][22]; 
void rmq()
{
	//初始化
    for(int i=1;i<=N;i++)
        dp[i][0]=a[i];
    for(int j=1;(1<<j)<=N;j++)
        for(int i=1;i+(1<<j)-1<=N;i++)
            dp[i][j]=min(dp[i][j-1],dp[i+(1<<j-1)][j-1]);
}
//询问
int query(int l,int r)
{
    int k=log2(r-l+1);
    return min(dp[l][k],dp[r-(1<<k)+1][k]);
}
 
int main()
{
	
	return 0;
 } 

例题:
牛客网:天才的记忆

在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+5;
int a[maxn];
int dp[200005][22]; 
int N,m;
void rmq()
{
	//初始化
    for(int i=1;i<=N;i++)
        dp[i][0]=a[i];
    for(int j=1;(1<<j)<=N;j++)
        for(int i=1;i+(1<<j)-1<=N;i++)
            dp[i][j]=max(dp[i][j-1],dp[i+(1<<j-1)][j-1]);
}
int query(int l,int r)
{
    int k=log2(r-l+1);
    return max(dp[l][k],dp[r-(1<<k)+1][k]);
}
 
int main()
{
	ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>N;
    for(int i=1;i<=N;i++) cin>>a[i];
    cin>>m;
    rmq();
    while(m--)
    {
        int l,r;
        cin>>l>>r;
        cout<<query(l,r)<<'\n';
    }
	return 0;
 } 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

行码棋

码字好辛苦,总结好吃力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值