ST表基础

目录:

一.先导

二.简介

三.解法思想

四.实战例题

五.总结


一.先导:
 在看ST表之前,先看一个问题:
 在序列2 4 1 5 3在这个序列中找出区间[1,3]、[3,5]、[1,5]的max
max[1, 3] = 4,max[3, 5] = 5,max[1, 5] = 5
 大家可以发现,在区间[1,3]中最大值是4,区间[3,5]中最大值是5,在整个区间[1,5]中最大值是5,
 max(a,b) = max(max(a,c), max(c,b)); (a<=c<=b)
 这个性质我们称之为:可重复性贡献
 如果我们单单通过这个倍增思想的话,每次询问的时间复杂度仍认为O(logn),显然这并没有得到优化。
但是基于以上性质再结合我们倍增的思想(每次前进2^ i), 使用两个预处理过的区间来覆盖询问区间,时间复杂度就被降至O(1)。


二.简介:
1:ST表(Sparse Table,稀疏表)是一种简单的数据结构
主要用来解决RMQ(Range Maximum / Minimum Query,区间最大 / 最小值查询)问题。
它主要应用倍增的思想,可以实现O(nlogn)预处理和O(1)查询。
2:RMQ问题
以最大值为例,是假如有一个数列A,给你一个区间[l,r],求max(Ai)(i属于[l,r])
3.优缺点
 如果仅仅进行区间最值查询,ST表的效率完全吊打线段树;
 但是,相比于线段树,ST表并不支持修改操作,无论是单点修改还是区间修改都不支持。


三.解法思想(以max为例)
 (1)ST表使用一个二维数组f,对于范围内的所有f[a][b],先算出并存储max(Ai)(i属于[a,a+2^b])
 首先:
 这称为预处理:

int f[maxn][21]; // 第二维的大小根据数据范围决定,不小于log(maxn)
for (int i = 1; i <= n; ++i)
 f[i][0]=read(); // 读入数据
for (int i = 1; i <= 20; ++i)
for (int j = 1; j + (1 << i) - 1 <= n; ++j)   //1<<i==2的i次方
f[j][i] = max(f[j][i - 1], f[j + (1 << (i - 1))][i - 1]);


1)原理:
 首先:
令f(j, i)表示从a[j] 开始连续2^ i个数的最大值,区间表示为[j,j+2 ^ i- 1];
显然,f[j][0] = a[j];
第二维则表示:倍增时跳的步数(2^ i - 1),跨越的长度。
通过以上的分析,我们得到一个状态转化方程:
max[l, r] = max(f[l][r - 1], f[l + (1 << (r - 1)][r - 1]);
 把区间平分为两部分,前半段按定义当然是从j到j+2^(i-1)-1的最大值
 后半段则是从j+2^(i-1)到j+2^(i-1)+2^(i-1)-1=j+2^i

举例:起初i=1:max[1,2],max[2,3]....i=2:max[2,4]...可以自己推演一遍,理解更深

 (2)查询:
  查询时,我们需要找到两个[l,r]的子区间,他们的并集是[l,r](不必不相交)
 具体地说,我们要找的是一个整数s两个子区间分别为[l,l+2^s-1]和[r-2^s+1,l];


 我们希望前一个子区间的右端点尽可能接近r。 

当l+2^s-1=r时,有s=log2(r-l+1),但因为s是整数,
 所以我们取log2(r-l+1)的整数,每次计算log太花时间了,我们可以对log也进行一次递推的预处理:
 

for (int i = 2; i <= n; ++i)
   Log2[i] = Log2[i / 2] + 1;


 查询的代码如下:

 for (int i = 0; i < m; ++i){
     int l = read(), r = read();
     int s = Log2[r - l + 1];
     printf("%d\n", max(f[l][s], f[r - (1 << s) + 1][s]));
}


 
四.实战例题(ST表模板题)

 P3865 【模板】ST 表
 题目描述
给定一个长度为N的数列,和M 次询问,求出每一次询问的区间内数字的最大值。
输入格式
第一行包含两个整数
N, M,分别表示数列的长度和询问的个数。
第二行包含N 个整数(记为ai​),依次表示数列的第i 项。
接下来M 行,每行包含两个整数li​, ri​,表示查询的区间为[li​, ri​]。
输出格式
输出包含
M 行,每行一个整数,依次表示每一次询问的结果。
1<=N<=10^5,1<=M<=2*10^6,0<=ai<=10^9

#include<iostream>
using namespace std;
const int maxn = 1e5 + 10;
typedef long long ll;
ll a[maxn], Log2[maxn], Max[maxn][20];
ll read() {
    ll ans = 0;
    char c = getchar();
    while (!isdigit(c))
        c = getchar();
    while (isdigit(c)) {
        ans = ans * 10 + c - '0';
        c = getchar();
    }
    return ans;
}
int main()
{
    ios::sync_with_stdio(false);
    int n = read(), m = read();
    for (int i = 1; i <= n; i++) {
        Max[i][0] = read();
    }
    for (int i = 2; i <= n; i++) {
        Log2[i] = Log2[i / 2] + 1;
    }
    for (int i = 1; i <= 20; i++) {
        for (int j = 1; j + (1 << i) - 1 <= n; j++) {
            Max[j][i] = max(Max[j][i - 1], Max[j + (1 << (i - 1))][i - 1]);
        }
    }
    while (m--) {
        int l = read(), r = read();
        int s = Log2[r - l+1];
        cout << max(Max[l][s], Max[r - (1 << s) + 1][s]) << '\n';
    }
    return 0;
}


(洛谷P2880[USACO07JAN]平衡的阵容Balanced Lineup)
题目背景
每天, 农夫 John 的N(1 <= N <= 50, 000)头牛总是按同一序列排队.
 有一天, John 决定让一些牛们玩一场飞盘比赛.他准备找一群在对列中为置连续的牛来进行比赛.
 但是为了避免水平悬殊, 牛的身高不应该相差太大.
 John 准备了Q(1 <= Q <= 200, 000) 个可能的牛的选择和所有牛的身高(1 <= 身高 <= 1, 000, 000).
 他想知道每一组里面最高和最低的牛的身高差别.
输入格式
第1行:N, Q
第2到N + 1行:每头牛的身高
第N + 2到N + Q + 1行:两个整数A和B,表示从A到B的所有牛。(1 <= A <= B <= N)
输出格式
输出每行一个数,为最大数与最小数的差

#include<iostream>
#define maxn 50005
using namespace std;
int read()    //快读,由于输入输出数据一般很多,防止因为I/O被卡
{
    int ans = 0;
    char c = getchar();
    while (!isdigit(c))
        c = getchar();
    while (isdigit(c)){
        ans = ans * 10 + c - '0';
        c = getchar();
    }
    return ans;
}
int Log2[maxn], Min[maxn][17], Max[maxn][17];
int main()
{
    int n = read(), m = read();
    for (int i = 1; i <= n; ++i)
    {
        int x = read();
        Min[i][0] = x;
        Max[i][0] = x;
    }
    for (int i = 2; i <= n; ++i)
        Log2[i] = Log2[i / 2] + 1;
    for (int i = 1; i <= 16; ++i)
        for (int j = 1; j + (1 << i) - 1 <= n; ++j)
        {
            Min[j][i] = min(Min[j][i - 1], Min[j + (1 << (i - 1))][i - 1]);
            Max[j][i] = max(Max[j][i - 1], Max[j + (1 << (i - 1))][i - 1]);
        }
    for (int i = 0; i < m; ++i)
    {
        int l = read(), r = read();
        int s = Log2[r - l + 1];
        int ma = max(Max[l][s], Max[r - (1 << s) + 1][s]);
        int mi = min(Min[l][s], Min[r - (1 << s) + 1][s]);
        printf("%d\n", ma - mi);
    }
    return 0;
}

五.总结
其实ST表不仅能处理最大值 / 最小值,凡是符合结合律且可重复贡献的信息查询都可以使用ST表高效进行。
可重复贡献的意义在于,可以对两个交集不为空的区间进行信息合并。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值