ST表——RMQ区间最值查询

RMQ

所谓 R M Q ( R a n g e    M a x i m u m / M i n i m u m    Q u e r y ) RMQ(Range ~~Maximum/Minimum~~ Query) RMQ(Range  Maximum/Minimum  Query),即区间最值查询问题。该问题的解决方法有线段树, S T ST ST表等等。对于静态的 R M Q RMQ RMQ S T ST ST表是最佳的选择。但是 S T ST ST表无法解决动态的区间最值而线段树可以

Sparse Table

初始化

设数组 a a a是所求的序列, d p [ i ] [ j ] dp[ i ] [ j ] dp[i][j]表示从第 i i i个数开始连续 2 j 2^j 2j个数的最小值。刚开始 d p [ i ] [ 0 ] dp[ i ][ 0 ] dp[i][0] 初始化为 a [ i ] a[ i ] a[i],因为 2 j 2^j 2j一定是偶数,因此对于每一个 d p [ i ] [ j ] dp[i][j] dp[i][j],我们将它分为如下图所示的两部分:
在这里插入图片描述
因此状态转移方程为: d p [ i ] [ j ] = m a x { d p [ i ] [ j − 1 ] , d p [ i + ( 1 < < ( j − 1 ) ] [ j − 1 ] } dp[ i ][ j ] = max \{ dp[ i ][ j - 1 ] , dp[ i+(1<<(j-1) ][ j - 1 ] \} dp[i][j]=max{dp[i][j1],dp[i+(1<<(j1)][j1]}

查询

假如查询的区间为 [ L , R ] [ L , R ] [L,R],由于我们只保存 [ L , L + ( 1 < < ? ) ] [ L , L+(1<<?) ] [L,L+(1<<?)]的最值,因此我们需要找到一个最大整数数 k k k使得 ( 1 < < k ) < = R − L + 1 (1<<k)<=R-L+1 (1<<k)<=RL+1,然后我们查询两个个可能有重复的区间: [ L , L + ( 1 < < k ) ] [ L , L+(1<<k) ] [L,L+(1<<k)] [ R − ( 1 < < k ) + 1 , R ] [ R-(1<<k)+1 , R ] [R(1<<k)+1,R] 。如下图所示:
在这里插入图片描述
例如查询区间 [ 1 , 6 ] [ 1 , 6 ] [1,6],先查询了 [ 1 , 4 ] [ 1 , 4 ] [1,4],再查询 [ 3 , 6 ] [ 3 , 6 ] [3,6],这样合并之后的最大值仍然是所求区间的最大值

时间复杂度

初始化的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),查询的时间复杂度 O ( 1 ) O(1) O(1)

代码

洛谷ST表模板为例:

update:2020.9.18

之前查询时是 l o g 2 n log_2n log2n查询的,现在在洛谷会被卡,于是更改成了预处理,实际上注释的部分就是对区间长度求 l o g 2 ( r − l + 1 ) log_2(r-l+1) log2(rl+1)

int d[maxn][25], a[maxn], Log[maxn];

int n, m;

inline int read() {
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

inline void write(int a) {
    if (a < 0) putchar('-'), a = -a;
    if (a >= 10) write(a / 10);
    putchar(a % 10 + 48);
}

void init() {
    for (int i = 1; i <= n; i++) d[i][0] = a[i];
    for (int j = 1; (1 << j) <= n; j++) {
        for (int i = 1; i + (1 << j - 1) <= n; i++)  //注意这里是1<<j-1而不是1<<j
            d[i][j] = max(d[i][j - 1], d[i + (1 << j - 1)][j - 1]);
    }
    for (int i = 1; i <= n; i++) {
        Log[i] = log2(i);
    }
}

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

int main() {
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    //ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    n = read(), m = read();
    //scanf("%d%d",&n,&m);
    for (int i = 1; i <= n; i++) {
        a[i] = read();
        //scanf("%d",&a[i]);
    }
    int l, r;
    init();
    while (m--) {
        l = read(), r = read();
        //scanf("%d%d",&l,&r);
        write(query(l, r));
        putchar('\n');
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值