ST表--适用多查询O(1)

ST表类似树状数组,线段树这两种算法,是一种用于解决RMQ问题的离线算法。

预处理\(O(nlogn)\),查询\(O(1)\)

给出一个长度为\(N\)的数列,和\(M\)次查询,求出每一次询问的区间内数字的最大值

定义:

\(st[i][j]\)表示从\(i\)位置开始的\(2^j\)个数中的最大值,即\(st[i][j] = max(a_i,...a_{i + 2^j-1})\)

预处理

\(O(nlogn)\)

img

\(st[i][j]\)对应的\([i,i + 2^j - 1]\)分成\([i,i + 2^{j - 1} - 1]\)\([i+2^{j - 1},i+2^j-1]\)两部分

对应的是\(st[i][j - 1]\)\(st[i + (1 << j) - 1][j - 1]\)

\[st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]); \]

查询

\(O(1)\)

img

\([l,r]\)分成两个有交集的区间\([l,l + 2^k-1]\)\([r - 2^k + 1][r]\)

\[k = log2(r - l + 1)\\ ans = max(st[l][k],st[r - (1 << k) + 1][k]) \]

打表观察

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int N = 3e5 + 5;
int st[N][22];
int main(){
    int n = 20;
    for(int j = 1; j <= 20; j++)
        for(int i = 1; i + (1 << j) - 1 <= n; i++)
            printf("%d [%d,%d] [%d,%d]\n", 1 << (j - 1), i, i + (1 << (j - 1)) - 1, i + (1 << (j - 1)), i + (1 << j) - 1);
    return 0;
}

{{uploading-image-762726.png(uploading...)}}

可以观察其变化情况

模板

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int N = 1e5 + 5;
int st[N][20];
int query(int l, int r){
    int k = log2(r - l + 1);
    return max(st[l][k], st[r - (1 << k) + 1][k]);
}
int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
        scanf("%d", &st[i][0]);
    
  int k = log2(n / 2) + 1; //常数优化
    for(int j = 1; j <= 17; j++) // j in [1, k]
        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]);
    for(int i = 1; i <= m; i++){
        int l, r;
        scanf("%d%d", &l, &r);
        printf("%d\n", query(l, r));
    }
    return 0;
}

扩展

st表必定是一个单调函数

  • 维护最大值的st表 - 单调递增
  • 维护最小值的st表 - 单调递减

可以用二分去寻找满足条件的区间个数

遍历下左区间,然后二分在st表里查找右区间

同时,st表也可以去维护其他的,比如区间LCA

总之,对于区间RMQ问题,或者区间问题,多组查询,且不修改,ST表很好用

posted @ 2019-09-21 09:30  Emcikem  阅读(182)  评论(0)    收藏  举报