【模板】ST表

1.一维ST表

RMQ(Range Maximum/Minimum Query)问题,即区间最值查询问题,是指对于长度为 n n n 的数列 a [   ] a[\ ] a[ ],查询数列 a [   ] a[\ ] a[ ] 中下标在 [ i , j ] [i, j] [i,j] 里的最大值或最小值。

一般解决这类区间问题可以用线段树来做,比较通用,但是线段树的代码量有点多。

这里介绍ST(Sparse Table)算法。

ST算法是一个非常有名的在线处理RMQ问题的算法,它在 O ( n log ⁡ n ) O(n \log n) O(nlogn) 时间内进行预处理,然后可以在 O ( 1 ) O(1) O(1) 时间内回答每个查询,其本质是倍增与动态规划的思想。

要注意的是,ST算法只适用于静态区间求最值。如果是动态的,那还是乖乖使用线段树吧!

1.1 预处理

ST算法是基于倍增思想设计的在线算法,之所以称之为倍增,是因为算法记录从每个元素开始的连续长度为 2 k 2^k 2k 的区间中元素的最值。

类似于动态规划,规定 f [ i , j ] f[i,j] f[i,j] 表示从第 i i i 个元素起连续 2 j 2^j 2j 个数中的最大值,即记录区间 [ i ,   i + 2 j − 1 ] [i,\ i+2^j-1] [i, i+2j1] 中的最大值。

由定义可知, f [ i , j ] f[i,j] f[i,j] 一定包含偶数个数字,故把 f [ i , j ] f[i,j] f[i,j] 平均分成两段,从 i i i i + 2 j − 1 − 1 i+2^{j-1}-1 i+2j11 为一段, i + 2 j − 1 i+2^{j-1} i+2j1 i + 2 j − 1 i+2^j-1 i+2j1 为另一段,长度均为 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\left\{ f[i,\ j-1], \ \ \ f[i+2^{j-1},\ j-1] \right\} f[i,j]=max{f[i, j1],   f[i+2j1, j1]},其中 f [ i , 0 ] = a [ i ] f[i,0] = a[i] f[i,0]=a[i]

void init()
{
    // 数组下标从1开始
    for (int i = 1; i <= n; i++)
    {
        f[i][0] = a[i];
    }
    
    for (int j = 1; (1 << j) <= n; j++)
    {
        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]);
        }
    }
}

1.2 查询

对于一个查询区间 [ l , r ] [l,r] [l,r],只要找到一个或者多个 2 2 2 的整数倍长度的区间覆盖 [ l , r ] [l,r] [l,r],然后取这些区间最大值的最大值就是答案了。

如何把 [ l , r ] [l,r] [l,r] 覆盖完整?

  • 一种办法是把区间的长度按照二进制分成多个 2 2 2 的整数倍区间,显然这些区间是不重叠的,这样求多次最大值就能得到答案。不过这种分割增加了算法常数,一次查询可能就要求几十次最大值。

  • 还有种更好的方法,原理是:为了减少分割出的区间数量,允许区间重叠,这样所有的情况下最多只要两个区间就好了,见下图所示。

由此可见,这个算法的高明之处不是在于动态规划的建立,而是它的查询,它的查询效率是 O ( 1 ) O(1) O(1)

在这里插入图片描述

假设我们要求区间 [ l , r ] [l,r] [l,r] 中序列 a [   ] a[\ ] a[ ] 的最大值,找到一个数 k k k 使得 2 k < r − l + 1 2^k < r - l + 1 2k<rl+1,即 k = ⌊ log ⁡ 2 ( r − l + 1 ) ⌋ k = \lfloor \log_2(r-l+1) \rfloor k=log2(rl+1)⌋,由换底公式得 k = ⌊ ln ⁡ ( r − l + 1 ) ln ⁡ ( 2 ) ⌋ k = \lfloor \frac{\ln(r-l+1)}{\ln(2)} \rfloor k=ln(2)ln(rl+1)

这样,可以把这个区间分成两个部分: [ l , l + 2 k − 1 ] [l, l+2^k-1] [l,l+2k1] [ r − 2 k + 1 , r ] [r-2^k+1, r] [r2k+1,r]

我们发现,这两个区间恰好是刚刚已经初始化好的,前者对应的是 f [ l , k ] f[l, k] f[l,k],后者对应的是 f [ r − 2 k + 1 , k ] f[r-2^k+1, k] f[r2k+1,k]

这样,只要看这两个区间的最大值,就可以知道整个区间的最大值。

int query(int l, int r)
{
    int k = log(r - l + 1) / log(2);
    return max(f[l][k], f[r - (1 << k) + 1][k]);
}

2.二维ST表

一维RMQ问题是求一个数列 a [   ] a[\ ] a[ ] 中某个区间的最值,而二维RMQ问题是求一个 n × m n \times m n×m 的矩阵中某个子矩阵的最值。

2.1 初始化

f [ i ] [ j ] [ i i ] [ j j ] f[i][j][ii][jj] f[i][j][ii][jj] 表示以 ( i , j ) (i, j) (i,j) 为左上角、以 ( i + 2 i i − 1 , j + 2 j j − 1 ) (i+2^{ii}-1, j+2^{jj}-1) (i+2ii1,j+2jj1) 为右下角的矩阵内的最大值。

易知: f [ i ] [ j ] [ 0 ] [ 0 ] = a [ i ] [ j ] f[i][j][0][0] = a[i][j] f[i][j][0][0]=a[i][j]

假设 f [ i ] [ j ] [ i i ] [ j j ] f[i][j][ii][jj] f[i][j][ii][jj] 中的 i i ii ii 不为 0 0 0,那么 f [ i ] [ j ] [ i i ] [ j j ] = m a x ( f [ i ] [ j ] [ i i − 1 ] [ j j ] , f [ i + 2 i i − 1 ] [ j ] [ i i − 1 ] [ j j ] ) f[i][j][ii][jj] = max(f[i][j][ii-1][jj], f[i+2^{ii-1}][j][ii-1][jj]) f[i][j][ii][jj]=max(f[i][j][ii1][jj],f[i+2ii1][j][ii1][jj])

在这里插入图片描述

如果 i i ii ii 为 0,那么就按 j j jj jj 来求,即 f [ i ] [ j ] [ i i ] [ j j ] = m a x ( f [ i ] [ j ] [ i i ] [ j j − 1 ] , f [ i ] [ j + 2 j j − 1 ] [ i i ] [ j j − 1 ] ) f[i][j][ii][jj] = max(f[i][j][ii][jj-1] , f[i][j+2^{jj-1}][ii][jj-1]) f[i][j][ii][jj]=max(f[i][j][ii][jj1],f[i][j+2jj1][ii][jj1])

在这里插入图片描述

简单来说,就是将二维问题转变为一维问题求解。

void init()
{
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            f[i][j][0][0] = a[i][j];
        }
    }
    
    for (int ii = 0; (1 << ii) <= n; ii++)
    {
        for (int jj = 0; (1 << jj) <= m; jj++)
    	{
    	    if (ii == 0 && jj == 0) continue;
    	    
    	    for (int i = 1; i + (1 << ii) - 1 <= n; i++)
    	    {
    	        for (int j = 1; j + (1 << jj) - 1 <= m; j++)
    	        {
    	            if (ii)
    	            {
    	                f[i][j][ii][jj] = max(f[i][j][ii - 1][jj], f[i + (1 << ii - 1)][j][ii - 1][jj]);
    	            }
    	            else
    	            {
    	                f[i][j][ii][jj] = max(f[i][j][ii][jj - 1], f[i][j + (1 << jj - 1)][ii][jj - 1]);
    	            }
    	        }
    	    }
    	}
    }
}

2.2 查询

假设我们要求以 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) 为左上角、以 ( x 2 , y 2 ) (x_2,y_2) (x2,y2) 为右下角的矩形区域的的最大值。

  • k 1 = ⌊ log ⁡ 2 ( x 2 − x 1 + 1 ) ⌋ k_1 = \lfloor \log_2(x_2 - x_1 + 1) \rfloor k1=log2(x2x1+1)⌋,由换底公式得 k 1 = ⌊ ln ⁡ ( x 2 − x 1 + 1 ) ln ⁡ ( 2 ) ⌋ k_1 = \lfloor \frac{\ln(x_2-x_1+1)}{\ln(2)} \rfloor k1=ln(2)ln(x2x1+1)

  • k 2 = ⌊ log ⁡ 2 ( y 2 − y 1 + 1 ) ⌋ k_2 = \lfloor \log_2(y_2 - y_1 + 1) \rfloor k2=log2(y2y1+1)⌋,由换底公式得 k 2 = ⌊ ln ⁡ ( y 2 − y 1 + 1 ) ln ⁡ ( 2 ) ⌋ k_2 = \lfloor \frac{\ln(y_2 - y_1 + 1)}{\ln(2)} \rfloor k2=ln(2)ln(y2y1+1)

将目标矩形分成四小块,这四小块可能有重合部分,但它们共同构成了目标矩形:

  • f [ x 1 ] [ y 1 ] [ k 1 ] [ k 2 ] f[x_1][y_1][k_1][k_2] f[x1][y1][k1][k2]

  • f [ x 1 ] [ y 2 − 2 k 2 + 1 ] [ k 1 ] [ k 2 ] f[x_1][y_2-2^{k_2}+1][k_1][k_2] f[x1][y22k2+1][k1][k2]

  • f [ x 2 − 2 k 1 + 1 ] [ y 1 ] [ k 1 ] [ k 2 ] f[x_2-2^{k_1}+1][y_1][k_1][k_2] f[x22k1+1][y1][k1][k2]

  • f [ x 2 − 2 k 1 + 1 ] [ y 2 − 2 k 2 + 1 ] [ k 1 ] [ k 2 ] f[x_2-2^{k_1}+1][y_2-2^{k_2}+1][k_1][k_2] f[x22k1+1][y22k2+1][k1][k2]

那么,求解这四块的最大值即为最终结果。

在这里插入图片描述

int query(int x1, int y1, int x2, int y2)
{
    int k1 = log(x2 - x1 + 1) / log(2);
    int k2 = log(y2 - y1 + 1) / log(2);
    
    int tmp1 = max(f[x1][y1][k1][k2], f[x1][y2 - (1 << k2) + 1][k1][k2]);
    int tmp2 = max(f[x2 - (1 << k1) + 1][y1][k1][k2], f[x2 - (1 << k1) + 1][y2 - (1 << k2) + 1][k1][k2]);
    
    return max(tmp1, tmp2);
}
  • 12
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值