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

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

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

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

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

考虑单调栈,若要查询 [l,r] 的区间 max,将扫到 r 时的单调栈拿出来,栈里的元素下标递增排列,值递减排列。此时我们需要在单调栈中找到第一个下标 l 的数对应的数值,而这个数值就是区间 max(单调栈本身的意义这个东西啊)。正常情况下需要二分,但由于我们的序列长只有 O(logn),所以我们考虑将单调栈用 int 状压,1 表示在单调栈里,0 表示不在。为了能取到 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),但是由于数据随机,发生的概率为 Bn,故期望复杂度为 O(B2n),然而实际测试取 B=n 跑的更快一些。常数比上面的小。

作者:dcytrl

出处:https://www.cnblogs.com/dcytrl/p/18589268

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   dcytrl  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
历史上的今天:
2024-01-28 一个题
2024-01-28 CF337E Divisor Tree
2024-01-28 Gym104270E Kawa Exam
2024-01-28 CF1924D Balanced Subsequences
more_horiz
keyboard_arrow_up light_mode palette
选择主题
点击右上角即可分享
微信分享提示