实战数据结构理解ST表

整体结构

前言

最近学习了ST表,所以写下这篇文章来记录一下,如有理解不到位的,欢迎大佬们在评论区留言

定义

ST是Sparse table的缩写,所以ST表是稀疏表;ST表本质是利用分治和倍增思想来求解的表,实战中通过预处理数据来得到一张表,然后通过查表得到结果;预处理可以做到O(nlogn),查询为O(1)

原理

之前说到,ST表就是用分治和倍增来求解得到的表,可以先看看洛谷这道题的题干https://www.luogu.com.cn/problem/P3865

分治和倍增体现在哪里呢?其实倍增体现在DP定义中,分治体现在递推公式中,ST表代码是利用了DP来进行预处理的

对于这道题,定义dp[i][j]表示左端点为i右端点为i+(2^j)-1的区间最大值

这里搬一下大佬的图
在这里插入图片描述
仔细观察区间就会发现[i,i+(2^j)-1]的区间长度正好是2的j次方,并且图示正好分一半,也就是一个区间分成两部分,每个部分都是2的j-1次方

所以可以这么理解,想求整个区间的最值,那么需要先求分开的两个区间的最值进行比较
这样就可以知道dp[i,j] = max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);

既然定义和递推公式已经知道了,那么如何初始化和遍历呢?

从递推公式就可以知道,必须保证j>=1,也就是要求j=0时dp[i][0]等于什么;还记得定义是什么吗?dp[i][0]就表示为左端点i,右端点i+2^0-1区间的最大值。意思已经很明显了,端点本身这个数就是最值了;所以初始化时需要将dp[i][0]全部初始化为自身这个数

那么遍历又如何?是先i后j还是先j后i?

首先需要明确的是,j是与倍增直接相关的,所以必然有i + (1 << j) - 1 <= n;也就是说倍增不能够超出总的数字个数,但在代码中的实际含义应该是和范围有关,所以应该这么理解:i倍增后的右区间不能超过n,所以这个式子存在于i层,j层判断则是根据实际的数据规模进行判断,不难发现,必须要先遍历j再遍历i,否则上述式子没办法取值

对于询问应该怎么做?询问能做到O(1)必不可少就是查表

由于打表的时候是利用倍增思想打表,那么查表的时候自然也是要利用倍增的思想查表
所以先求log2(区间数字个数),再查表,查表时要覆盖左端点和右端点,所以分别以左端点和右端点开始做倍增,这样就一定能查到最值,下面这张图应该很清楚了,还是大佬的图

在这里插入图片描述
可以算一下,右区间-左区间+1就是闭区间的数字个数,比如:[0,5]的数字个数是5-0+1=6个

除开ST表的求解外,本题的数据非常严格,要求能预处理的都要预处理,所以还要设置一个对数数组来预处理,防止重复计算对数来优化询问时间

最后附上代码:

#include<cstdio>
#include<iostream>
using namespace std;
const int N = 100001;
int i, j, m, n, l, r, lg[N], dp[N][22]; //lg代表对数数组,存放处理的对数;dp[i][j]表示左闭区间i开始的2^j个数的最大值
int que(int x, int y)
{
    int z = lg[y - x + 1];
    return max(dp[x][z], dp[y - (1 << z) + 1][z]);
}
int main()
{
    cin >> n >> m;
    lg[0] = -1;
    for (i = 1; i <= n; ++i)
    {
        scanf("%d", &dp[i][0]); //sacnf性能更高
        lg[i] = lg[i >> 1] + 1; //预处理
    }
    for (j = 1; j < 22; ++j)
        for (i = 1; i + (1 << j) - 1 <= n; ++i)
            dp[i][j] = max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
    for (i = 1; i <= m; ++i)
    {
        scanf("%d%d", &l, &r);
        printf("%d\n", que(l, r));
    }
    return 0;
}

总结

ST表类问题的解决方法就是利用倍增的思想进行打表,查询时直接查表,注意数据规模就基本可以得到解决

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值