ST表是一种对数组的特殊的应用技巧,是一种处理max(a,a) = a或者有这种类似性质函数的数据结结构
静态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<
//即两个子区间连续相邻不重复,但是不能出现空白的地方
原题链接:P3865 【模板】ST 表 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
完整代码:
#include
#include
#include
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<
//是因为,我们举个例子,假如r==8 1<
//如果不+1,那么代码的含义就是从4开始,长度为4的点的最大值
//点包含4 5 6 7,很明显,8这个点不在范围内,但是边界应该包含8,所以我们要+1
//这里的len也就是第二维是区间长度
}
return 0;
}
动态的维护一段序列,初始序列是空,边插入边求一段区间的最大值。可以使用动态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<
//若是不-1.那么代码代表的含义就是,从5这个点开始,向左四个长度。即是5 4 3 2 这四个点的最大值,但是我们必须要把1这个起点也包含进去,显然不-1就没法包含
原题链接:[P1198 JSOI2008] 最大数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
完整代码:
#include
#include
#include
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;
}