什么是ST表

引入

  • 最朴素的问题是这样的,给定 n n n个数,现在想求出 k 1 k_1 k1 k 2 k_2 k2的区间最大值,其中 1 ≤ k 1 ≤ k 2 ≤ n 1\leq k_1\leq k_2\leq n 1k1k2n
  • 这样的问题其实是普遍的,最常见的例子就是高考,所有考生的成绩已经都知道了,如何求某个区间段内的最高分,便于报考

分析

  • 此类问题都属于RMQ(Range Minimum Query)问题,也就是区间范围最值问题,朴素的方法是 f o r for for一圈,寻找区间最值,这种方法的问题在于对于多个查询,比如 1 0 6 10^6 106次查询,如果区间长度也是 1 0 6 10^6 106,那么时间复杂度的数量级是 1 0 12 10^{12} 1012

解法

模板题

DP

  • 朴素算法的最大问题就是每一次都需要一次 f o r for for来寻找最优解,那么可以考虑能否采用备忘录思想,将最优解存储下来,以便于 O ( 1 ) O(1) O(1)的求出每一次查询的区间最值
  • d p [ i ] [ j ] dp[i][j] dp[i][j]表示从第 i i i个元素到第 j j j个元素的区间最大值,设 D a t a [ i ] Data[i] Data[i]为第 i i i个元素,那么显然问题具有最优子结构性质,也就是 i i i j j j的区间最大值可以由 i i i j − 1 j-1 j1的区间最大值推出,所以很容易列出状态转移方程为 d p [ i ] [ j ] = m a x ( d p [ i ] [ j − 1 ] , D a t a [ i ] ) dp[i][j]=max(dp[i][j-1],Data[i]) dp[i][j]=max(dp[i][j1],Data[i])这时候,查询时间复杂度是 O ( 1 ) O(1) O(1),但这带来了一个新的问题,如果要查询第 1 0 6 10^6 106 1 0 7 10^7 107元素之间的最大值,那么空间显然无法达到,也就是算法空间复杂度太高,同时,从时间复杂度上看,我们需要两圈 f o r for for循环,时间复杂度是 O ( n 2 ) O(n^2) O(n2),也不能够达到要求,需要考虑优化

倍增优化

  • 考虑到上面算法的第二维设置成了区间终点,那么换一种思路,考虑设置为和区间长度相关的量,如果直接设置为区间长度,空间优化也不大,所以采用倍增思想,令 d p [ i ] [ j ] dp[i][j] dp[i][j]表示为起点为 i i i,区间长度为 2 j 2^j 2j的区间的最大值,那么应该如何进行状态转移呢?
  • 设需要查询的区间长度为 l e n len len s i z e = l o g 2 ( l e n ) size=log2(len) size=log2(len),区间左端点为 l l l,右端点为 r r r d p [ i ] [ j ] dp[i][j] dp[i][j]表示为起点为 i i i,区间长度为 2 j 2^j 2j的区间的最大值那么有 l e n = r − l + 1 len=r-l+1 len=rl+1,考虑将 d p [ i ] [ j ] dp[i][j] dp[i][j]划分为这样两个区间, d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1] d p [ i + 2 j − 1 ] [ j − 1 ] dp[i+2^{j-1}][j-1] dp[i+2j1][j1],对应于查询区间为 [ i , i + 2 j ] [i,i+2^{j}] [i,i+2j]划分为 [ i , i + 2 j − 1 ] [i,i+2^{j-1}] [i,i+2j1] [ i + 2 j − 1 ] [ i + 2 j ] [i+2^{j-1}][i+2^j] [i+2j1][i+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 + 2 j − 1 ] [ j − 1 ] ) dp[i][j]=max(dp[i][j-1],dp[i+2^{j-1}][j-1]) dp[i][j]=max(dp[i][j1],dp[i+2j1][j1])当然,初值应该为 d p [ i ] [ 0 ] = D a t a [ i ] , dp[i][0]=Data[i], dp[i][0]=Data[i]这样 d p dp dp数组就填充完了
  • 接下来考虑如何使用 d p dp dp数组,如果问题是求区间 [ l , r ] [l,r] [l,r]的最大值,需要在 [ l , r ] [l,r] [l,r]内进行一次划分,划分为 [ l , l + 2 s i z e ] [l,l+2^{size}] [l,l+2size] [ r − 2 s i z e + 1 , r ] [r-2^{size}+1,r] [r2size+1,r]这样两个集合之间必然有交点,考虑集合两端点的大小可证明得到,这里略去,那么取这两部分的最大值也就得到了 d p [ i ] [ j ] dp[i][j] dp[i][j],这样查询的时间复杂度就优化到了 O ( 1 ) O(1) O(1)
  • 这个 d p dp dp数组就是ST表,将 d p dp dp数组转换为ST数组如下
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e6 + 100;
const double eps = 1e-6;
int ST[MAXN][64];
int main(){
    //freopen("input.txt", "r", stdin);
    //freopen("output.txt", "w", stdout);
    int n, m, l, r;
    scanf("%d%d", &n, &m);
    for(int i=1;i<=n;i++) scanf("%d", &ST[i][0]);
    for(int j=1;j<=log2(n);j++){
        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]);
        }
    }
    while(m--){
        scanf("%d%d", &l, &r);
        int len = r - l + 1;
        int sz = log2(len);
        printf("%d\n", max(ST[l][sz], ST[r - (1 << sz) + 1][sz]));
    }
    return 0;
}

练习

洛谷

  • 求区间长度为m的区间最大值,起点已知
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e6 + 100;
const double eps = 1e-6;
int ST[MAXN][64];
int main(){
    //freopen("input.txt", "r", stdin);
    //freopen("output.txt", "w", stdout);
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i=1;i<=n;i++) scanf("%d", &ST[i][0]);
    for(int j=1;j<=log2(n);j++){
        for(int i=1;i+(1 << (j - 1)) <= n;i++){
            ST[i][j] = min(ST[i][j - 1], ST[i + (1 << (j - 1))][j - 1]);
        }
    }
    for(int i=1;i<=n-m+1;i++){
        int len = m;
        int j = i + len - 1;
        int sz = log2(len);
        printf("%d\n", min(ST[i][sz], ST[j - (1 << sz) + 1][sz]));
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clarence Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值