【学习笔记】RMQ问题与Sparse Table算法

RMQ问题

RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:对于长度为n的数列A,回答若干次询问RMQ(i,j),返回数列A中下标在区间[i,j]中的最小/大值。

ST算法

ST(Sparse Table,稀疏表,ST表)是用来解决RMQ问题的一种算法,预处理复杂度O(nlogn),查询复杂度O(1),不支持在线修改后查询

构造

ST表的构造采用了动态规划的思想,
状态表示: s t [ i ] [ j ] = m a x k = j j + 2 i − 1 a [ k ] st[i][j] = max_{k=j}^{j+2^i-1}a[k] st[i][j]=maxk=jj+2i1a[k],即从j到j+2^i-1的最大(或最小)值。
初始状态: s t [ 0 ] [ j ] = a [ j ] st[0][j] = a[j] st[0][j]=a[j]
状态转移: s t [ i ] [ j ] = m a x ( s t [ i − 1 ] [ j ] , s t [ i − 1 ] [ j + 2 i − 1 ] ) st[i][j] = max(st[i-1][j], st[i-1][j+2^{i-1}]) st[i][j]=max(st[i1][j],st[i1][j+2i1])
i 取 1 到 l o g n , j 取 1 到 n i取1到logn,j取1到n i1lognj1n,一共 O ( n l o g n ) O(nlogn) O(nlogn)个状态,每次转移复杂度 O ( 1 ) O(1) O(1),总构造复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

查询

我们已经知道以任意一点开始了长度为2的幂次的区间最大值,如果要求长度为L的[x,y]区间内的最大值,就可以求[x,x+k],[y-k+1,y]这两个区间最大值的最大值,其中k是小于L的最大2的幂次。
m a x ( s t [ t ] [ x ] , s t [ t ] [ y − 2 t + 1 ] ) , 其 中 t = l o g 2 ( y − x + 1 ) max(st[t][x],st[t][y-2^t+1]),其中t=log2(y-x+1) max(st[t][x],st[t][y2t+1])t=log2(yx+1)
log函数可以预处理一个数组,每次查询复杂度 O ( 1 ) O(1) O(1)

代码

学习了一些C++类的知识,静态成员必须在类外定义及初始化,在类中只是声明,且在类外用双冒号表示这是一个类的静态成员。
另外,vector初始化第一个参数表示大小,第二个表示初始值,所以二维vector的初始化方式:vector<vector<int>> vvi(outsize, vector<int>(insize))
(9月27日改)使用std::function实现了自定义比较函数。

class SpraseTable
{
    static int lg[M];
    int n;
    function<int(int,int)> cmp;
    vector<vector<int>> table; //table[i][j]表示长度为2^i的以j开头的数组最值
public:
    SpraseTable(int *arr, int _n, 
        function<int(int,int)> _cmp = [](int a,int b){return a<b?a:b;}
    ) : n(_n), cmp(_cmp)
    {
        if(!lg[0]) {lg[0]=-1;for(int i=1;i<M;i++)lg[i]=lg[i/2]+1;}
        table = vector<vector<int>>(lg[n] + 1, vector<int>(n + 1));
        for(int i = 1; i <= n; i++)
            table[0][i] = src[i];
        for(int i = 1; i <= lg[n]; i++)
            for(int j = 1; j <= n; j++)
                if(j + (1 << i) - 1 <= n)
                    table[i][j] = cmp(table[i-1][j], table[i-1][j+(1<<(i-1))]);
    }
    inline int query(int x, int y)
    {
        t = lg[y - x + 1];
        return cmp(table[t][x], table[t][y - (1 << t) + 1]);
    }
};int SpraseTable::lg[M];

模板题

洛谷P3865 【模板】ST表

/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
inline int read(); inline void write(int x);
const int M = 100016, MOD = 1000000007;

class SpraseTable
{
    static int lg[M];
    int n;
    function<int(int,int)> cmp;
    vector<vector<int>> table; //table[i][j]表示长度为2^i的以j开头的数组最值
public:
    SpraseTable(int *arr, int _n, 
        function<int(int,int)> _cmp = [](int a,int b){return a<b?a:b;}
    ) : n(_n), cmp(_cmp)
    {
        if(!lg[0]) {lg[0]=-1;for(int i=1;i<M;i++)lg[i]=lg[i/2]+1;}
        table = vector<vector<int>>(lg[n] + 1, vector<int>(n + 1));
        for(int i = 1; i <= n; i++)
            table[0][i] = arr[i];
        for(int i = 1; i <= lg[n]; i++)
            for(int j = 1; j <= n; j++)
                if(j + (1 << i) - 1 <= n)
                    table[i][j] = cmp(table[i-1][j], table[i-1][j+(1<<(i-1))]);
    }
    inline int query(int x, int y)
    {
        int t = lg[y - x + 1];
        return cmp(table[t][x], table[t][y - (1 << t) + 1]);
    }
};int SpraseTable::lg[M];

int save[M];
int main(void)
{
    #ifdef _LITTLEFALL_
      freopen("in.txt","r",stdin);
    #endif

    int n=read(),m=read();
    for(int i=1;i<=n;i++)
        save[i] = read();
    SpraseTable spt = SpraseTable(save,n,[](int a,int b){return a>b?a:b;});
    while(m--)
    {
        int a = read(),b=read();
        printf("%d\n",spt.query(a,b));
    }
    return 0;
}


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*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void write(int x)
{
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
} 

其他

RMQ可以用来求LCA,下次再细学.
(9月27日补)已学,参见【笔记】dfs序,欧拉序,LCA的RMQ解法
ST表维护的运算需要满足什么性质?首先肯定是半群上的,然后为什么这种做法不能用来求区间和?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值