ST表(静态+动态)

ST表

ST表是一种对数组的特殊的应用技巧,是一种处理max(a,a) = a或者有这种类似性质函数的数据结结构

1 静态ST表

静态ST表的题目一般会先给出一个有n个数的序列,然后进行m次询问,每次询问会要求求出[l,r]区间最大的数字
由于一开始就已经给出了整数序列,所以我们只需要直接求出每个区间的最大值进行存储即可

算法思想

所有的数字都可以由 1 2 4 8 16 32 64等这种2的次方数组合得出,所以我们假设求一段序列的[ 3 , 10 ]区间的最大值,我们可以求[3,3+4]和[ 10-4,10] 这两个区间的最大值即可,哪怕有重复也没关系,因为max(a,a) = a

但是一定要确保至少两端区间有重复或者相邻连续,两段区间之间不能有空白

根据这个思想,我们可以用动态规划的思想,由子区间求完整区间的最大值 。动态规划最重要的是找到dp数组每一个维度代表的含义,我们用dp[ i ,j ]来表示,其中i表示从区间的起点,2^j表示区间长度

dp[i][0] = arr[0];//当长度只有1时,最大值就是他 本身  
dp[i][j] =  max(dp[i][j-1], dp[i+(1<<j-1)][j-1];

输出[l,r]区间长度的最大值可以采用上述的重叠方法

int len = log2(r-l+1);//为什么要加1,可以看这个例子
//若l=1,r=3,那么长度应该是3,但是r-l=2,所以要+1
cout<<max(dp[l][len],dp[r-(1<<len)+1][len]);//可能有重叠的地方,最坏的情况是l+len = r-(1<<len)+1
//即两个子区间连续相邻不重复,但是不能出现空白的地方

原题链接:P3865 【模板】ST 表 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

完整代码:

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;

const int N=1e6;
int n,m;//n个数,m次询问
int arr[N];//读入的序列
int dp[N][32];//dp数组,维护区间max,注意第二维代表的是2的次方长度
int main(){
    scanf("%d%d",&n,&m);//注意这题若使用cin会超时,必须使用更高效的读入方式和打印方式
    for(int i=0;i<n;i++)cin>>arr[i],dp[i][0]=arr[i];//区间长度为1是最大数等于本身
    
    //为什么i是内部循环,j是外部循环?
     //i是起点,j是区间长度,把i设为内部循环,是为了先计算相同长度,不同起点的区间最大值
     //这样先把短的区间最大值求出来,再求较长的区间的最大值
    //左边的短区间和右边的短区间可以合并成为这个长区间,达到了递推的效果
     //若是把i写在外面,那就是先计算同起点的不同长度的区间,但是长的区间并不能由短区间求出来
     //因为同起点的区间,长的无法完全由短的组成
     int le = log2(n);
    for(int j=1;j<=le;j++){ //j从1开始的原因是因为j==0的情况已经在前面处理了
        for(int i=0;i+(1<<j)<n+1;i++){ 
            //这里为什么要+1,是因为log2()会下取整,可能会少算一些
            dp[i][j] = max(dp[i][j-1],dp[i+(1<<j-1)][j-1]);
        }
    }
    while(m--){
        int l,r;
        scanf("%d%d",&l,&r);
       int len = log2(r-l+1);
        r--,l--;
        printf("%d\n",max(dp[l][len],dp[r-(1<<len)+1][len]));//这里为什么用r-(1<<len)+1要+1
     //是因为,我们举个例子,假如r==8 1<<len==4 len==2
     //如果不+1,那么代码的含义就是从4开始,长度为4的点的最大值
     //点包含4 5 6 7,很明显,8这个点不在范围内,但是边界应该包含8,所以我们要+1
     //这里的len也就是第二维是区间长度
    }
    return 0;
}

2 动态ST表

动态的维护一段序列,初始序列是空,边插入边求一段区间的最大值。可以使用动态ST表

这种动态ST表只应用于起点确定的序列

算法思想:

根据ST表可以倒叙保存的思想,我们可以从后开始保存ST表

dp[ i , j ] 的含义为由i为起点,向左区间长度为2^j次放的整数序列的最大值

由此我们可以得出,dp数组的预处理和最大值

dp[i][j]  = max(dp[i][j-1],dp[i-(1<<(j-1))][j-1];
 cout<<max(dp[l+(1<<len)-1][len],dp[r][len]);
   //这里为什么要-1呢,可以举个例子 
   //假设现在l==1  1<<len==4 len==2
   //若是不-1.那么代码代表的含义就是,从5这个点开始,向左四个长度。即是5 4 3 2 这四个点的最大值,但是我们必须要把1这个起点也包含进去,显然不-1就没法包含

原题链接:[P1198 JSOI2008] 最大数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

完整代码:

#include<iostream>
#include<vector>
#include<cmath>
using namespace std;

long long int n,m;

vector<int>arr;
long long int dp[(int)2e5+10][30];

void bulid(int u){
    dp[u][0] = arr[u];
    //因为这道题查询的终点一定在最后一个元素
    //那我们可以倒叙保存st表,达到起点永远是最后一个元素的效果
    //这样就保证了起点一直唯一,那么我们就只需要遍历第二维度的长度
    //总结就是把末尾元素看成起点,只计算起点的区间,所以一维的就可以
    for(int i =1;(1<<i)<=u;i++)
        dp[u][i] = max(dp[u][i-1],dp[u-(1<<(i-1))][i-1]);  //倒着存储st表  
}

int search(int l,int r){
    if(l==arr.size()-1)return arr[arr.size()-1];
   long long  int len = (int)log2(r-l+1);

    return max(dp[r][len],dp[l+(1<<len)-1][len]);
}

int main(){
    arr.push_back(1);
   long long  int c = 0;//记录上次查询到的结果
    cin>>n>>m;
    while(n--){
        char a;//操作
        cin>>a;
        
       long long  int b; //查询操作的长度或者是要加的数
        
        if(a=='A'){
            //插入操作
            cin>>b;
            arr.push_back((b+c)%m);
            bulid(arr.size()-1);//创建dp数组
        }
        else {
          cin>>b;  //查询的长度
           c=search(arr.size()-b,arr.size()-1);
            cout<<c<<endl;
        }
    }
    return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

海风许愿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值