ST表(RMQ问题)
算法理解
RMQ问题就是对与区间最值查询一类问题的总称
对于RMQ问题的求解主要有以下两种方式:
- 线段树,建树O(n),查询O(logn),支持在线修改
- ST表,预处理O(logn),查询O(1),不支持在线修改
这个单元主要讲解的是ST表
倍增思想
考虑一个数必然能被拆分成二进制,所以我们先预处理出
ST表
本质上是对区间进行一个拆分,拆成一块一块,然后对块进行查询
正常的分块一个块长是
结合倍增思想,我们选择将块长定为
预处理
因为静态询问所以我们先预处理出这个倍增数组,因为
查询
我们想要查询
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,m,l,r;
int a[N],dp[N][40];
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
dp[i][0]=a[i];
}
for(int j=1;j<=32;j++){
for(int i=1;i+(1ll<<j)-1<=n;i++){
dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
}
for(int i=1;i<=m;i++){
scanf("%lld%lld",&l,&r);
int k=log2(r-l+1);
printf("%lld\n",max(dp[l][k],dp[r-(1<<k)+1][k]));
}
}
T1:
注意只要用到long long 你的位运算就要用
T2:
直接把max改成gcd即可
考虑两个区间可以合并,当且仅当区间重复计算并不会对答案产生影响,因为查询时两个区间是重叠的
那我可不可以将ST表改成倍增我对区间(l,r),进行一个二进制拆分,然后使得区间查询时并不重复,就能实现O(logn)静态维护区间加区间乘呢?(问题暂且保留)
突然发现自己唐氏了,您是否再找前缀和?!!
T3:
ST表比较复杂的一道题
首先我们先不考虑区间查询,我们只找以第i个数结尾的最长不同区间的长度
这个是很好求的只需要记录一下
然后考虑一下对于一个询问
因为在
我们只需要二分的找到now的位置,显然在now之前的区间对于(l,r)的贡献是
所以最后答案统计即为
注意几点:
1.数组下标从0开始
2.a[i]存在负数,要用map
T4:
经典二维题(我差亿点就做出来了)
预处理
先初始化
然后再考虑线性做法,从
我们先推出当x的还没开始倍增时(即
查询
我们将一个区间拆成4个可重区间
然后求得答案即可
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=255,M=40;
int n,m,k,l1,l2,r1,r2;
int a[N][N],dp[N][M][N][M];
signed main(){
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%lld",&a[i][j]);
dp[i][0][j][0]=a[i][j];
}
}
for(int j1=0;j1<=31;j1++){
for(int j2=0;j2<=31;j2++){
if(!j1&&!j2) continue;
for(int i1=1;i1+(1ll<<j1)-1<=n;i1++){
for(int i2=1;i2+(1ll<<j2)-1<=m;i2++){
if(!j1) dp[i1][j1][i2][j2]=max(dp[i1][j1][i2][j2-1],dp[i1][j1][i2+(1ll<<(j2-1))][j2-1]);
else dp[i1][j1][i2][j2]=max(dp[i1][j1-1][i2][j2],dp[i1+(1ll<<(j1-1))][j1-1][i2][j2]);
}
}
}
}
for(int i=1;i<=k;i++){
scanf("%lld%lld%lld%lld",&l1,&r1,&l2,&r2);
int k1=log2(l2-l1+1),k2=log2(r2-r1+1);
int ans=max(max(dp[l1][k1][r1][k2],dp[l2-(1ll<<k1)+1][k1][r1][k2]),max(dp[l1][k1][r2-(1ll<<k2)+1][k2],dp[l2-(1ll<<k1)+1][k1][r2-(1ll<<k2)+1][k2]));
printf("%lld\n",ans);
}
}
T5:
使用二分+st表,但是需要离散化,我懒,每写离散化,喜提80pts,固输过的
T6:
哈哈,笑死,我看到这道题的查询区间和竟然想到对ST表的查询进行二进制拆分,怀着发现新大陆的心情去隔壁机房问唐老师
然后唐老师说:前缀和直接做就做完了(っ °Д °;)っ
呃然后我又码了一个二维st表板子上去,喜提60+MLE,然后打开题解,才发现有一条特殊性质
每个询问的方阵的较长边不超过较短边的两倍
考虑我们先想一下这个特殊的性质,也就是说这个区间的
简单来说就是将
预处理
把一个正方形拆成4个子正方形合并答案,因为子正方形的边长一定时正方形边长的一半,所以满足合并关系
查询
先将矩形沿长边拆成两个正方形,再对正方形进行拆成4个可重正方形,进行答案统计即可
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=805;
int n,m,q,l1,l2,r1,r2;
int a[N][N],num[N][N],sum[N][N],mx[N][N][16],mn[N][N][16];
char s[N];
int maxans(int l1,int r1,int l2,int r2){
int k=log2(min(l2-l1+1,r2-r1+1));
return max(max(mx[l1][r1][k],mx[l1][r2-(1<<k)+1][k]),max(mx[l2-(1<<k)+1][r1][k],mx[l2-(1<<k)+1][r2-(1<<k)+1][k]));
}
int minans(int l1,int r1,int l2,int r2){
int k=log2(min(l2-l1+1,r2-r1+1));
return min(min(mn[l1][r1][k],mn[l1][r2-(1<<k)+1][k]),min(mn[l2-(1<<k)+1][r1][k],mn[l2-(1<<k)+1][r2-(1<<k)+1][k]));
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
num[i][j]=num[i][j-1]+a[i][j];
sum[i][j]=sum[i-1][j]+num[i][j];
mx[i][j][0]=a[i][j];
mn[i][j][0]=a[i][j];
}
}
for(int k=1;k<=15;k++){
for(int i=1;i+(1<<k)-1<=n;i++){
for(int j=1;j+(1<<k)-1<=m;j++){
mx[i][j][k]=max(max(mx[i][j][k-1],mx[i+(1<<(k-1))][j][k-1]),max(mx[i][j+(1<<(k-1))][k-1],mx[i+(1<<(k-1))][j+(1<<(k-1))][k-1]));
mn[i][j][k]=min(min(mn[i][j][k-1],mn[i+(1<<(k-1))][j][k-1]),min(mn[i][j+(1<<(k-1))][k-1],mn[i+(1<<(k-1))][j+(1<<(k-1))][k-1]));
}
}
}
scanf("%d",&q);
for(int i=1;i<=q;i++){
scanf("%s",s+1);
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
l1++,l2++,r1++,r2++;
if(s[1]=='S'){
printf("%d\n",sum[l2][r2]+sum[l1-1][r1-1]-sum[l1-1][r2]-sum[l2][r1-1]);
}
else if(s[2]=='A'){
int c1=l2-l1,c2=r2-r1;
if(c1>c2){
printf("%d\n",max(maxans(l1,r1,l1+c2,r2),maxans(l2-c2,r1,l2,r2)));
}
else{
printf("%d\n",max(maxans(l1,r1,l2,r1+c1),maxans(l1,r2-c1,l2,r2)));
}
}
else{
int c1=l2-l1,c2=r2-r1;
if(c1>c2){
printf("%d\n",min(minans(l1,r1,l1+c2,r2),minans(l2-c2,r1,l2,r2)));
}
else{
printf("%d\n",min(minans(l1,r1,l2,r1+c1),minans(l1,r2-c1,l2,r2)));
}
}
}
}
T7:
咋一眼看:板子题
2h后。。。
真tm屎
先看年份的值域
然后对于一个查询
T8:
首先求和转化为前缀和相减,也就是固定右端点找到区间内左端点的最小值,我们先将所有右端点所对应的最小左端点找出来,然后放在堆中维护一个四原组
T9:
第一次没有想出来,复习时想了好久终于想出来了
首先发现,这棵二叉树有一个性质,在我们确定某一个位置上是哪一个节点时,它的返祖链上最后一条向右的边确定了它的值域下界,最后一条向左的边确定了它的值域上界,然后我们再在值域范围中,找到下标最小的一个,就是这个位置的值
所以我们先将按照数值拍一个序,然后用我们依次找到二叉树的各个位置是哪一个点,因为我们已经知道我们二叉树要找到哪个点,所以我们一定已经知道这个点所处的上下界,只需要在序列中找到下标的最小值即可,用st表维护即可,至于实现,可以用dfs实现
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探