O(n)-O(1) 线性 RMQ 学习笔记

O(n)-O(1) 线性 RMQ 学习笔记

O(n) 预处理,O(1) 查询的 RMQ(区间最值)算法。

而我们正常 ST 表处理 RMQ 只能做到 O(nlogn)O(1)

用四毛子算法可以做到 O(nloglogn)O(1)

四毛子算法:对原序列分块,块长为 O(logn),块之间做 ST 表,每个块内做 ST 表(不能预处理前缀和后缀最值,因为可能有左右端点在一个块内的情况)。

但四毛子算法还是不够优秀。

那么下面我们介绍线性 RMQ 算法(也叫标准 RMQ 算法)。

1.建笛卡尔树

我们对原序列建出笛卡尔树,至于是大根还是小根依据具体题目。

我们可以使用单调栈 O(n) 建出笛卡尔树。

笛卡尔树的性质:区间 [L,R] 最值位于笛卡尔树上 L,RLCA

于是区间最值转化为了求 LCA

2.欧拉序转化 LCA

对笛卡尔树建出欧拉序。

欧拉序:初始一个空的序列,在 dfs 的过程中,每个点进入时将它加入序列末尾,离开时将它的父亲加入序列末尾,特别地,根节点离开时不加入它的父亲(它没有父亲),欧拉序是一个长为 2n1 的序列。

欧拉序的性质:设 firsti 表示点 i 第一次在欧拉序中出现的位置,lasti 为最后一次出现的位置,则 i,j(firsti<firstj)LCA 为欧拉序上 [firsti,lastj] 区间内深度最小的点。

这是一个 ±1 RMQ 问题,也就是相邻两个元素的值刚好相差 1

以上的主要思想就是把一般 RMQ 问题转化为 ±1 RMQ 问题。

3.±1 RMQ 问题

做法 1

t 为欧拉序列的长度,即 t=2n1

我们采用 分块 + ST 表 + 散块状压 来解决这个问题,其中 分块 + ST 表 就是四毛子算法的思想。

取块长 B=O(logn),对欧拉序列分块,则有 t/B 个块。

对于每个块之间做 ST 表,时间复杂度 O(t)

而对于散点,我们并不能预处理每个块的前缀和后缀最值,因为可能出现左端点和右端点在同一个块里的情况。

我们对每个块的每个前缀以原数组的值维护一个单调栈,单调栈可以用 0/1 状压哪些元素在单调栈中,那么查询一个块的 [l,r] 最值时,就是找前缀 r 的单调栈中,第一个下标大于等于 l 的元素,即第 l 位后第一个 1

做法 2

考虑 CSP-S2021第一轮 中的做法。

t 还是如做法 1 的定义。

还是分块,块长我们取 B=logt2,对于大块如做法 1。

而对于块内的 RMQ,一个块内的差分数组只有 2B1 种,预处理所有差分数组的最值位置,预处理复杂度 O(B2B1),不超过 O(n),而查询就是 O(1) 了。

例题

由乃救爷爷

直接用朴素的四毛子是过不了的。

我们可以考虑线性 RMQ,但这里提供一种不用线性 RMQ 的做法,常数较优秀。

考虑用一种另类的四毛子算法,对于散点用前缀和后缀最值处理。

然而对于两端点在同一个块内的情况,因为数据随机,所以两端点在一个块内的概率是 Bn,此时直接暴力做是 O(B),同一块内的期望复杂度就是就是 O(B2n)

总的复杂度就是 O(n/Blog(n/B)+QB2n),当 nQ 同阶时,Blognn 之间时,复杂度都是线性的。

建议取 n 因为处理 ST 表很慢。

这道题的代码:

#include<bits/stdc++.h>
using namespace std;
namespace IO{
const int sz=1<<22;
char a[sz+5],b[sz+5],*p1=a,*p2=a,*t=b,p[105];
inline char gc(){
return p1==p2?(p2=(p1=a)+fread(a,1,sz,stdin),p1==p2?EOF:*p1++):*p1++;
}
template<class T> void read(T& x){
x=0; char c=gc(),fushu=0;
for(;c<'0'||c>'9';c=gc())if(c=='-')fushu=1;
for(;c>='0'&&c<='9';c=gc())
x=x*10+(c-'0');
if(fushu)x=-x;
}
inline void flush(){fwrite(b,1,t-b,stdout),t=b; }
inline void pc(char x){*t++=x; if(t-b==sz) flush(); }
template<class T> void write(T x,char c='\n'){
if(x<0) pc('-'), x=-x;
if(x==0) pc('0'); int t=0;
for(;x;x/=10) p[++t]=x%10+'0';
for(;t;--t) pc(p[t]); pc(c);
}
struct F{~F(){flush();}}f;
};
#define fo(i,l,r) for(int i=(l);i<=(r);++i)
#define fu(i,l,r) for(int i=(l);i<(r);++i)
#define fd(i,r,l) for(int i=(r);i>=(l);--i)
#define IOS ios::sync_with_stdio(0); cin.tie(0)
#define filein(x) freopen(x,"r",stdin)
#define fileout(x) freopen(x,"w",stdout)
#define usefile(x) freopen(x ".in","r",stdin); freopen(x ".out","w",stdout)
#define ll long long
#define ld long double
#define ull unsigned long long
//using IO::read;
//using IO::write;
namespace GenHelper
{
unsigned z1,z2,z3,z4,b;
unsigned rand_()
{
b=((z1<<6)^z1)>>13;
z1=((z1&4294967294U)<<18)^b;
b=((z2<<2)^z2)>>27;
z2=((z2&4294967288U)<<2)^b;
b=((z3<<13)^z3)>>21;
z3=((z3&4294967280U)<<7)^b;
b=((z4<<3)^z4)>>12;
z4=((z4&4294967168U)<<13)^b;
return (z1^z2^z3^z4);
}
}
void srand(unsigned x)
{using namespace GenHelper;
z1=x; z2=(~x)^0x233333333U; z3=x^0x1234598766U; z4=(~x)+51;}
int read()
{
using namespace GenHelper;
int a=rand_()&32767;
int b=rand_()&32767;
return a*32768+b;
}
const int N=2e7+5;
int n,m,SEED;
ull ANS;
const int B=4472;
int a[N],b[N],c[N],ans[13][N/B+2],lg[B+10];
signed main(){
// usefile("rmq");
cin>>n>>m>>SEED;
srand(SEED);
fo(i,1,n){
a[i]=read();
int t=i/B;
if(a[i]>ans[0][t])ans[0][t]=a[i];
b[i]=a[i];
if((i-1)/B==i/B)b[i]=max(b[i-1],b[i]);
}
fd(i,n,1){
c[i]=a[i];
if((i+1)/B==i/B)c[i]=max(c[i+1],c[i]);
}
fo(i,2,n/B+1)lg[i]=lg[i>>1]+1;
fo(j,1,12)fo(i,0,n/B-(1<<j)+1)ans[j][i]=max(ans[j-1][i],ans[j-1][i+(1<<j-1)]);
fo(i,1,m){
int l=read()%n+1,r=read()%n+1;
if(l>r)swap(l,r);
int as=0;
if(l/B==r/B){
fo(j,l,r)as=max(as,a[j]);
}
else{
int L=l/B+1,R=r/B-1;
int len=R-L+1;
if(len>0)as=max(ans[lg[len]][L],ans[lg[len]][R-(1<<lg[len])+1]);
as=max(as,max(c[l],b[r]));
}
ANS+=as;
}
cout<<ANS;
return 0;
}
posted @   dengchengyu  阅读(263)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示