[学习笔记] rmq 问题

时间:2021-08-12
本文章向大家介绍[学习笔记] rmq 问题,主要包括[学习笔记] rmq 问题使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

0. 前言

以前只会 \(\mathcal O(n\log n)-\mathcal O(1)\) 的朴素 \(\rm rmq\)… 一度以为这就是 \(\rm rmq\) 的全部…

回想起来自己真是 \(\rm naive\) 得可爱。

放一道 \(\rm rmq\) 的题:由乃救爷爷,之后的内容应该就在这题上测了…

1. 前缀后缀分块

1.1. 算法流程

将序列划分成 \(\frac{n}{B}\) 个块,对于每个 \(i\),预处理出它在所属块的前缀 \(\max\) 与后缀 \(\max\)。再对块与块之间求一个朴素 \(\rm rmq\)

查询就是出现散块就将前缀后缀拼在一起,整块就朴素 \(\rm rmq\)。注意还有 \(l,r\) 在同一块内的情况,这个直接暴力查询。

1.2. 复杂度分析

不太好分析的是 \(l,r\) 在同一块内的情况。枚举块共 \(\frac{n}{B}\) 种情况,那么 \(l,r\) 都在此块的概率为 \(\frac{B^2}{n^2}\),所以总概率是 \(\frac{B}{n}\),单次复杂度是 \(\mathcal O(\frac{B^2}{n})\)。所以总共是 \(\mathcal O(n+\frac{n}{B}\log \frac{n}{B}+q+q\cdot \frac{B^2}{n})\)

\(B\) 至少为 \(\log n\) 时,\(\frac{n}{B}\log \frac{n}{B}\) 不超过 \(n\);当 \(B\) 至多为 \(\sqrt n\) 时,\(q\cdot \frac{B^2}{n}\) 不超过 \(n\)

所以 \(B\)\(\log n\sim \sqrt n\) 时即可。还有一个 出题人心理学分析,还蛮有意思的。

可以看作是 \(\mathcal O((n+q)\cdot \sqrt{\log n})\) 的。另外,听说块长设成 \(2^k\) 会快一些。

1.3. 代码

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f|=(s=='-');
	while(s>='0' and s<='9')
		x=(x<<1)+(x<<3)+(s^48),
		s=getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-'),write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
} 

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;
}

#include <iostream>
using namespace std;

const int B=64,maxn=20000005;

int n,m,pre[maxn],suf[maxn];
int st[20][maxn/B+2],bl[maxn];
int lg[maxn/B+2],a[maxn];
unsigned s;

inline int Max(const int a,const int b) { return a>b?a:b; }

int calc(int l,int r) {
	if(l>r) swap(l,r);
	int ret=0;
	if(bl[l]^bl[r]) {
		ret=Max(suf[l],pre[r]);
		l=bl[l]+1,r=bl[r]-1;
		if(l<=r) {
			int d=lg[r-l+1];
			ret=Max(ret,Max(st[d][l],st[d][r-(1<<d)+1]));
		}
	}
	else {
		for(int i=l;i<=r;++i)
			ret=Max(ret,a[i]);
	}
	return ret;
}

void init() {
	for(int i=1;i<=n;++i)
		if((bl[i]-1)*B+1==i)
			pre[i]=a[i];
		else
			pre[i]=Max(pre[i-1],a[i]);
	suf[n]=a[n];
	for(int i=n-1;i>=1;--i)
		if(bl[i]*B==i)
			suf[i]=a[i];
		else
			suf[i]=Max(suf[i+1],a[i]);
	lg[0]=-1;
	for(int i=1;i<=bl[n];++i)
		lg[i]=lg[i>>1]+1,
		st[0][i]=pre[i*B];
	for(int j=1;(1<<j)<=bl[n];++j)
		for(int i=1;i+(1<<j)-1<=bl[n];++i)
			st[j][i]=Max(st[j-1][i],st[j-1][i+(1<<j-1)]);
}

int main() {
	n=read(9),m=read(9),s=read(9);
	srand(s);
	for(int i=1;i<=n;++i)	
		a[i]=read(),
		bl[i]=(i-1)/B+1;
	int l,r; init();
	unsigned long long ans=0;
	while(m--) {
		l=read()%n+1;
		r=read()%n+1;
		ans+=calc(l,r);
	}
	print(ans,'\n');
	return 0;
}

2. 压位分块

2.1. 算法流程

\(B\) 设为 \(\log n\),对块与块之间求一个朴素 \(\rm rmq\)。对每个块从左到右维护递减的单调栈,注意由于将状态压缩成一个数字,我们要求字长 \(w\ge B\)。具体一点就是如果某个下标在单调栈内,就将数字对应那一位赋值为 \(1\)。查询块内 \((l,r)\) 时就查询 \(r\) 对应的单调栈状态满足 \(i\ge l\) 的第一个为 \(1\) 的数字即可。可以用 __builtin_ctz()\(\mathcal O(1)\) 内计算。

2.2. 复杂度分析

朴素 \(\rm rmq\) 只用 \(\frac{n}{\log n}\) 个数,复杂度 \(\mathcal O(n)\)。单调栈预处理 \(\mathcal O(n)\),查询 \(\mathcal O(q)\)

但是模板题还不如前缀后缀分块跑得快就是了。可能是我人丑常数大吧。

2.3. 代码

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f|=(s=='-');
	while(s>='0' and s<='9')
		x=(x<<1)+(x<<3)+(s^48),
		s=getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-'),write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
} 

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;
}

inline int Max(int x,int y) {int m=(x-y)>>31; return y&m|x&~m;}

#include <iostream>
using namespace std;

const int B=32,maxn=20000005;

int n,m,l,r,S[B+5],tp;
int a[maxn],lg[maxn/B+2];
int st[20][(int)9e5];
unsigned s;
struct Stack {
	int sta[B+5],delta;
	
	int QMax(int l,int r) {
		return a[delta+l+__builtin_ctz(sta[r]>>l-1)];
	}
	
	void ins(int *A) {
		tp=0;
		for(int i=1;i<=B;++i) {
			sta[i]=sta[i-1];
			while(tp and A[S[tp]]<=A[i])
				sta[i]^=(1<<S[tp]-1),--tp;
			S[++tp]=i,sta[i]^=(1<<i-1);
		}
	}
} p[(int)9e5];

void init() {
	int len=(n-1)/B+1; lg[0]=-1;
	for(int i=1;i<=len;++i) {
		st[0][i]=a[(i-1)*B+1];
		for(int j=(i-1)*B+2;j<=i*B;++j)
			st[0][i]=Max(st[0][i],a[j]);
		lg[i]=lg[i>>1]+1;
		p[i].ins(a+(i-1)*B),p[i].delta=(i-1)*B;
	}
	for(int j=1;(1<<j)<=len;++j)
		for(int i=1;i+(1<<j)-1<=len;++i)
			st[j][i]=Max(st[j-1][i],st[j-1][i+(1<<j-1)]);
}

int rmq(int l,int r) {
	int d=lg[r-l+1];
	return Max(st[d][l],st[d][r-(1<<d)+1]);
}

int calc() {
	int a=(l-1)/B+1,b=(r-1)/B+1,ret=0;
	if(a+1<=b-1) ret=rmq(a+1,b-1);
	if(a==b) ret=p[a].QMax(l-(a-1)*B,r-(a-1)*B);
	else ret=Max(ret,Max(p[a].QMax(l-(a-1)*B,B),p[b].QMax(1,r-(b-1)*B)));
	return ret;
}

int main() {
	n=read(9),m=read(9),s=read(9);
	srand(s);
	for(int i=1;i<=n;++i)	
		a[i]=read();
	init();
	unsigned long long ans=0;
	while(m--) {
		l=read()%n+1,r=read()%n+1;
		if(l>r) swap(l,r);
		ans+=calc();
	}
	print(ans,'\n');
	return 0;
}

原文地址:https://www.cnblogs.com/AWhiteWall/p/15134821.html