P3865 【模板】ST 表(ST表学习)

【模板】ST 表

传送门:

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

思路

学习ST表之前最重要的还是了解倍增的思路
与其说是倍增这种高深的词汇,还不如就说十进制转二进制,二进制转十进制

十进制和二进制的互转,无非其重点就是任意十进制都可以转换成任意二进制,并且可以利用二进制每一位只为0 or 1的特性来使得题目的 时间复杂度空间复杂度 大幅度降低

我们以求区间最大值来引入ST表

例题:

给定一个长度为 N 的数列,和 M 次询问,求出每一次询问的区间内数字的最大值。

遇到这种题目是不是想直接暴力

for(int i=l;i<=r;i++){//1次最大值
	MAX=max(MAX,a[i]);
}

但是n和m很大呢?r比l大很多呢?重复询问很多呢? 是不是肯定超时

有人说打表,打表虽然虽然可以解决重复询问和每次计算的过程,但是如果n,m的值很大,预处理的时候就已经超时了

好了这时候我们可以引入ST表了

我们可以求每段长度的一半的最大值进行比较然后最后求出最大值
二叉树
(画的比较丑勿喷)

以这种方式求区间最大值,第二层的第一个结点就是由他的两个子节点比较得出,第二层的第二个结点也是由他的两个子节点比较得出;然后往上往上一直到得出区间最大值为止。(实际上是个递归的过程,就是从最长的那个区间往下二分二分,分到每个结点只有一个数然后回溯,回溯;但是为了方便理解用dp来求出st表,所以用递推的方式描述)

这里大家可能有很多问题,,,,

  1. 为什么这是一直二分就可以求出最大值,那原来的数的个数不是2的次幂呢?

    其实是没有关系的,如果我的二叉树中比如说,第二层第一个结点实际上有5个数,那是不是按照二分思路,他的两个子节点各包含3个结点(这边因为宁愿多数也不愿少数,多数可以处理,少数的话还要加一条分支就不能是二叉树了,实现细节这里不要纠结),那么事实上现在这两个子节点一共由6个数,有一个数是重复的,那这个重复的数是肯定不会影响第二层第一个结点的最大值的,思考一下,如果重复的是一个小的数那是不是不影响最大值,如果重复的是一最大值,那最大值还是最大值。

  2. 为什么跟其他st表的题解有点不一样?

    这里直接写了优化后的,如果是非优化前,需要以树的思路来思考,码量和时间复杂度都要提高。这边我们直接讲二叉树和优化后的思路,如果有兴趣,可以熟练了之后回去看看。

如果还有问题欢迎在评论区私信!!!

开始步入正途!!!

构建ST表

//在这之前我们要知道dp[i][j]表示什么?(前面铺垫的二叉树和进制互转就是为了理解这个,当然那只是为了理解折半线段后重复数不影响最大值的求解,当然那里不理解我们接着往下看也能理解这道题为什么这样做)
//表示以第i个数到第i+2^j-1个数的最大值
void InitSt(){
	rep(i,1,n) dp[i][0]=a[i];//初始化第i到第i+0个数(也就是只有自己的时候)的最大值就是自己
	for(int j=1;j<=log2(n);j++){//上图中已描述二叉树的思路,这边实则就是二叉树的高度-1
	//为什么是要-1呢?
	//因为j表示第第j步推到第j+1的步骤,
		for(int i=1;i+(1<<j)-1<=n;i++){
			dp[i][j]=max(dp[i][j-1],dp[i+(1<<j-1)][j-1]);
			//那么第i个数到第i+2^j-1个数的最大值就是第i个数到第i+2^(j-1)个数和第i+2^(j-1)到i+(2^j-1)个数的较大值,那是不是左边的一半和右边的一半(这里讲的一半是有可能有重复数的,因为我们只有了二分而不是将每一部分的j作比较)比较的较大值就是i到i+2^j-1的最大值
		}
	}
}

构建完了ST表是不是还有问题?

  1. 上面括号里讲的这个(这里讲的一半是有可能有重复数的,因为我们只有了二分而不是将每一部分的j作比较)是什么意思因为j表示的是2j,所以每一部分的j就表示的是 2a+2b+2c(这里abc表示不同时刻的j)也就是此时是没有重复部分的,正好上面二进制之和是原十进制数的个数(进制转换!!!思考思考)。

  2. 为什么要将这个for(int j=1;j<=log2(n);j++)放在外面,for(int i=1;i+(1<<j)-1<=n;i++)放在里面?
    理解广搜的思路,如果换了顺序就是深搜的思路,因为该二叉树的上层的最大值是与子节点的最大值相关的,所以不适合用深搜。

这题的关键还是对于这个状态转移方程的理解,可以自己配合画二叉树来理解

查询

int query(int l,int r){
	int x=(int)log2(r-l+1);//2^x就是表达包含的数的个数那么我们r-l+1==2^x,x就等于这个了
	return max(dp[l][x],dp[r-(1<<x)+1][x]);//这一步不讲了吧,与上面状态转移方程的思路是一样的
}

总结

构建ST表实则就是构建二叉树的每一个结点,
而查询实则就是找两个匹配的结点比较

这里思路有问题可以评论或私信我,大家一起学习进步!

AC code

// Problem: 
//     P3865 【模板】ST 表
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3865
// Memory Limit: 125 MB
// Time Limit: 800 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<algorithm>
//#include<cstdio>
#include<cmath>
#define ll long long 
#define endl '\n'
#define rep(i,a,b) for(int i=(a);i<=(b);i++)//表示遍历[a,b]
#define per(i,a,b) for(int i=(a);i>(b);i--)
#define N 100010 //1e6+100 
#define M 20
using namespace std;
int n,a[N],m;
int dp[N][M];//表示当前第i个数到第i+2^j-1个数的最大值
void InitSt(){
	rep(i,1,n) dp[i][0]=a[i];//初始化第i到第i+0个数的最大值就是自己
	for(int j=1;j<=log2(n);j++){//优化即,找两段的最大值然后再算最大值,详细考虑过程思路中已说明
		for(int i=1;i+(1<<j)-1<=n;i++){
			dp[i][j]=max(dp[i][j-1],dp[i+(1<<j-1)][j-1]);
		}
	}
}

int query(int l,int r){
	int x=(int)log2(r-l+1);//两端最大,其实跟建立st表的那个状态转移方程是一样的
	return max(dp[l][x],dp[r-(1<<x)+1][x]);
}
int main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);//输入数据量较大如果用cin cout输入输出是需要关闭输出流的
	cin>>n>>m;
	rep(i,1,n) cin>>a[i];
	int l,r;
	InitSt();
	rep(i,1,m){
		cin>>l>>r;
		cout<<query(l,r)<<endl;
	}
	return 0; 
} 



有问题请评论或私信我,谢谢!

有共同学习需求的可以加入洛谷团队:https://www.luogu.com.cn/team/66731

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值