前言

“***,你会四毛子算法吗?不会的话就去学会,给同学们讲讲”

简介

四毛子算法 (Four Russian) 是一个由四位俄罗斯人毛子提出的一种可以在 $O(n \log \log n) - O(1)$ 复杂度内解决RMQ问题的算法。

算法流程

首先把序列分块,对于每块内部维护一个ST表,对于不同的块之间再维护一个ST表。

每次查询一个 $[l,r]$ 的时候:

假设l所在的区间为 $[l_1,r_1]$ ,r所在的区间为 $[l_2,r_2]$。

若l和r处于同一块内,直接查询块内的ST表即可。

若l和r处于相邻块内,则直接查询 $[l,r_1]$ 和 $[l_2,r]$ 的最小值即可。

否则,我们把区间 $[l,r]$ 分成三段:

1: $[l,r_1]$

2: $[r_1+1,l_2-1]$

3: $[l_2,r]$

其中第二个区间直接查询不同块之间的ST表,第一个和第三个区间块内查询即可。

复杂度分析

查询复杂度显然为 $O(1)$。

设块长为k,则有预处理复杂度:

$$O(\frac{n}{k} \log \frac{n}{k}+n \log k)$$

我们取 $k=\log n$ ,就得到了复杂度 $O(n \log \log n)$ 。

空间复杂度也为 $O(n \log \log n)$ 。

算法改进

我们发现原版四毛子算法的常数非常大,因为每次询问可能要跑3个ST表。

考虑改进这个过程。我们发现块内查询相当于查询前缀/后缀最小值,所以我们在预处理的过程中,对于每个块暴力预处理出它们的前缀和后缀min。这样询问只需要跑一个ST表,常数大大减小。

代码

真不难写(逃

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include<bits/stdc++.h>
using namespace std;
int n,m,blo,tot,lc[1000005],rc[1000005],bel[1000005],a[1000005],log1[1000005];
inline void init(){
log1[1]=0;
for(int i=2;i<=1000000;++i)log1[i]=log1[i>>1]+1;
blo=log1[n];
for(int i=1;i*blo<=n;++i)lc[++tot]=blo*(i-1)+1,rc[tot]=blo*i;
if(n%blo){
int tmp=n/blo;
lc[++tot]=tmp*blo+1,rc[tot]=n;
}
for(int i=1;i<=tot;++i)
for(int j=lc[i];j<=rc[i];++j)bel[j]=i;
}
struct stable{
int f[18][18],lf[18],rf[18],len;
void init(int l,int r){
len=r-l+1;
memset(lf,0x3f,sizeof lf);
memset(rf,0x3f,sizeof rf);
for(int i=1;i<=len;++i)f[i][0]=a[l+i-1];
for(int j=1;(1<<j)<=len;++j)
for(int i=1;i+(1<<j)-1<=len;++i)
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
for(int i=1;i<=len;++i)lf[i]=min(lf[i-1],f[i][0]);
for(int i=len;i>=1;--i)rf[i]=min(rf[i+1],f[i][0]);
}
inline int askm(int l,int r){
int x=log1[r-l+1];
return min(f[l][x],f[r-(1<<x)+1][x]);
}
inline int askl(int r){return lf[r];}
inline int askr(int l){return rf[l];}
inline int ask(int l,int r){
if(l<1||r>len||l>r)return -1;
if(l==1)return askl(r);
else if(r==len)return askr(l);
else return askm(l,r);
}
};
struct st_table{
stable s[100005];
int len,f[100005][25];
void init(int n){
len=tot;
for(int i=1;i<=len;++i)s[i].init(lc[i],rc[i]);
for(int i=1;i<=len;++i)f[i][0]=s[i].ask(1,s[i].len);
for(int j=1;(1<<j)<=len;++j)
for(int i=1;i+(1<<j)-1<=len;++i)
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
inline int ask(int l,int r){
int x=bel[l],y=bel[r],z=log1[y-x-1],ansl=1e9,ansr;
if(x==y)return s[x].ask(l-lc[x]+1,r-lc[x]+1);
if(x!=y-1)ansl=min(f[x+1][z],f[y-(1<<z)][z]);
ansr=min(s[x].ask(l+s[x].len-rc[x],s[x].len),s[y].ask(1,r-lc[y]+1));
return min(ansl,ansr);
}
}s;
int main(){
ios::sync_with_stdio(0);
cin>>n>>m;init();
for(int i=1;i<=n;++i)cin>>a[i];
s.init(n);
for(int i=1;i<=m;++i){
int l,r;cin>>l>>r;
cout<<s.ask(l,r)<<'\n';
}
return 0;
}

提示

请注意,该四毛子算法常数仍然巨大,不保证可以通过模板题(但是loj的过了而且比ST表快