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

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

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

而我们正常 ST 表处理 RMQ 只能做到 \(O(n \log n)-O(1)\)

用四毛子算法可以做到 \(O(n\log\log n)-O(1)\)

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

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

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

1.建笛卡尔树

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

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

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

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

2.欧拉序转化 LCA

对笛卡尔树建出欧拉序。

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

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

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

以上的主要思想就是把一般 RMQ 问题转化为 \(\pm1\) RMQ 问题。

3.\(\pm1\) RMQ 问题

做法 1

\(t\) 为欧拉序列的长度,即 \(t=2n-1\)

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

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

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

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

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

做法 2

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

\(t\) 还是如做法 1 的定义。

还是分块,块长我们取 \(B=\lceil\frac{\log t}{2}\rceil\),对于大块如做法 1。

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

例题

由乃救爷爷

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

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

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

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

总的复杂度就是 \(O(n/B\log (n/B)+Q\frac{B^2}{n})\),当 \(n\)\(Q\) 同阶时,\(B\)\(\log n\)\(\sqrt n\) 之间时,复杂度都是线性的。

建议取 \(\sqrt 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 @ 2024-08-18 16:56  dengchengyu  阅读(133)  评论(0编辑  收藏  举报