ST表算法总结

55 篇文章 0 订阅
37 篇文章 0 订阅

RMQ问题

RMQ (Range Minimum/Maximum Query):对于长度为n的数组A,回答若干询问RMQ(A,i,j)(i,j<=n-1),返回数组A中下标在i,j范围内的最小(大)值,即求解区间最值

求解方法

线段树 预处理O(n) ~ 查询O(log(n))
ST(Sparse Table)表(本质dp)预处理O(nlog(n)) ~ 查询O(1),不支持在线修改。

ST表算法

首先建立st表,
s t [ i ] [ j ] st[i][j] st[i][j]表示从第i个数起连续 2 j 2 ^ j 2j 个数中的最大值,即区间[ i , i + 2 j − 1 i, i+2^j-1 i,i+2j1]内最大值。
显然,初始状态 s t [ i ] [ 0 ] = a [ i ] st[i][0] = a[i] st[i][0]=a[i],就是第 i i i 个数本身。

我们把 s t [ i ] [ j ] st[i][j] st[i][j]分成两部分( s t [ i ] [ j ] st[i][j] st[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]为一段,长度都为 2 j − 1 2^{j-1} 2j1
显然 s t [ i , j ] = m a x ( s t [ i , j − 1 ] , s t [ i + 2 j − 1 , j − 1 ] ) st[i , j] = max(st[i , j-1],st[ i + 2^{j-1}, j-1]) st[i,j]=maxst[i,j1]st[i+2j1,j1]
上式就是状态转移方程。

接着是查询,因为st存的状态长度都是 2 j 2^j 2j,但是给出的查询区间长度不一定等于 2 j 2^j 2j
给出定理 2 ⌊ l o g 2 a ⌋ &gt; a 2 2^{\lfloor log_2{a} \rfloor} &gt; \frac{a}{2} 2log2a>2a
对于给定区间 [ x , y ] [x, y] [x,y]
k = ⌊ l o g 2 ( y − x + 1 ) ⌋ k =\lfloor log_2{(y-x+1)} \rfloor k=log2(yx+1)
根据上面定理, 2 k 2^k 2k 肯定刚好大于区间长度的一半,
所以x 到 y 的最大值等于max(从x往后 2 k 2^{k} 2k 的最大值,从y往前 2 k 2^{k} 2k的最大值)
r m q ( x , y ) = m a x ( s t [ x ] [ k ] , s t [ y − 2 k + 1 ] [ k ] ) rmq(x, y) = max(st[x][k] , st[y-2^k+1][k]) rmq(x,y)=max(st[x][k],st[y2k+1][k])
k值还可以这样解释,假设拆分成的2个子区间,每个子区间都有2^(k-1)个元素。则2个子区间分别为:
[ i , 2 k − 1 + i − 1 ] [i, 2^{k-1} + i -1] [i,2k1+i1] [ j − 2 k − 1 + 1 , j ] [j - 2^{k-1} + 1,j] [j2k1+1,j]。显然必须要满足2个子区间能够完全覆盖[i,j],即 ( 2 k − 1 + i − 1 ) + 1 &gt; = ( j − 2 k − 1 + 1 ) (2^{k-1} + i -1) + 1 &gt;= (j - 2^{k-1} +1) 2k1+i1+1>=j2k1+1
进而可以推导出 2^k >= (j - i + 1)。这样的话,我们取满足条件的K最小值就可以了。为什么要取最小值呢?是为了保证2个子区间长度尽可能的短。

例题51nod1174

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define d(x) cout << (x) << endl
#pragma GCC diagnostic error "-std=c++11"
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 1e4 + 10;

int n;
int a[N];
int st[N][20];

void rmq()
{
    for(int j = 1; (1 << j) <= n; j++){     //按列填表
        for (int i = 0; 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 = log2(r - l + 1);
    return max(st[l][k], st[r - (1 << k) + 1][k]);
}
int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i++){
        scanf("%d", &a[i]);
        st[i][0] = a[i];        //初始化
    }
    rmq();
    int q;
    cin >> q;
    while(q--){
        int i, j;
        cin >> i >> j;      //询问区间[i, j]
        cout << ask(i, j) << endl;
    }
    // d(st[0][1]);
    return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值