loading

一种 O(n)-O(1) 的 RMQ 算法

https://www.luogu.com/article/ey6cxlv5

考虑将原序列分块,块长为 \(B=O(\log n)\),那么对这些整块做 ST 表的复杂度就是 \(O(\frac{n}{B}\log\frac{n}{B})\) 的,而这个东西小于 \(O(n)\)

而对于散块,预处理一下块内的前缀后缀最大值即可。

但是这个算法会在 \(l,r\) 落在同一个块内时出现问题。若暴力复杂度仍会带 log。

考虑单调栈,若要查询 \([l,r]\) 的区间 max,将扫到 \(r\) 时的单调栈拿出来,栈里的元素下标递增排列,值递减排列。此时我们需要在单调栈中找到第一个下标 \(\ge l\) 的数对应的数值,而这个数值就是区间 max(单调栈本身的意义这个东西啊)。正常情况下需要二分,但由于我们的序列长只有 \(O(\log n)\),所以我们考虑将单调栈用 int 状压,1 表示在单调栈里,0 表示不在。为了能取到 \(\ge l\) 的数,我们先把 \(<l\) 的那些东西用二进制右移扔掉,然后就相当于找到从低到高位第一个 1 出现的位置,这就是 __builtin_ctz 干的事情,然后就可以 \(O(1)\) 了。

code(P3793):

点击查看代码
const int maxn=2e7+5,maxm=8e5+5,inf=0x3f3f3f3f,B=25;
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()
{
//	return rd<int>();
    using namespace GenHelper;
    int a=rand_()&32767;
    int b=rand_()&32767;
    return a*32768+b;
}
int n,Q,a[maxn],num;
inline int bel(int x){
	return (x-1)/B+1;
}
inline int getl(int x){
	return (x-1)*B+1;
}
inline int getr(int x){
	return min(x*B,n);
}
int f[maxm][25],lg[maxm];
int sta[maxn],tp;
int st[maxn];
void init(){
	rep(i,2,num)lg[i]=lg[i>>1]+1;
	const int M=lg[num];
	rep(j,1,M)rep(i,1,num-(1<<j)+1)f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
int qry_1(int l,int r){
	const int L=getl(bel(l));
	return a[l+__builtin_ctz(st[r]>>(l-L))];
}
int qry_2(int l,int r){
	if(l>r)return ~0x7fffffff;
	int p=lg[r-l+1];
	return max(f[l][p],f[r-(1<<p)+1][p]);
}
int sec[maxn],pre[maxn];
inline void solve_the_problem(){
	unsigned s;
	n=rd<int>(),Q=rd<int>(),s=rd<unsigned>();
	srand(s);
	rep(i,1,n)a[i]=read();
	num=(n+B-1)/B;
	rep(k,1,num){
		const int L=getl(k),R=getr(k);
		int	mx=0;
		rep(i,L,R)mx=max(mx,a[i]);
		f[k][0]=mx;
		pre[L]=a[L];
		rep(i,L+1,R)pre[i]=max(pre[i-1],a[i]);
		sec[R]=a[R];
		per(i,R-1,L)sec[i]=max(sec[i+1],a[i]);
		tp=0;
		int lst=0;
		rep(i,L,R){
			while(tp&&a[i]>a[sta[tp]])lst^=(1<<(sta[tp]-L)),--tp;
			lst|=(1<<(i-L)),sta[++tp]=i,st[i]=lst;
		}
	}
	init();
	u64 ans=0;
	while(Q--){
		int l=read()%n+1,r=read()%n+1;
		if(l>r)swap(l,r);
		if(bel(l)==bel(r))ans+=qry_1(l,r);
		else ans+=max({sec[l],pre[r],qry_2(bel(l)+1,bel(r)-1)});
	}
	write(ans);
}

当数据随机的时候(就是 P3793),考虑这样一种做法:

整块处理部分和上面一样,当 \(l,r\) 落在一个块内的时候,考虑暴力,复杂度为 \(O(B)\),但是由于数据随机,发生的概率为 \(\frac{B}{n}\),故期望复杂度为 \(O(\frac{B^2}{n})\),然而实际测试取 \(B=\sqrt n\) 跑的更快一些。常数比上面的小。

posted @ 2025-01-28 17:03  dcytrl  阅读(59)  评论(1)    收藏  举报