介绍
莫队算法是由莫涛提出的算法,是一种处理区间问题的暴力数据结构,时间复杂度为 O ( n n ) O(n \sqrt{n}) O(nn)。
问题引入
给定一个序列 A 1 , A 2 , … , A n A_1,A_2,\dots,A_n A1,A2,…,An 和 Q Q Q 个询问 ( L i , R i ) (L_i,R_i) (Li,Ri),询问 A L i , A L i + 1 , … , A R i A_{L_i},A_{L_i+1},\ldots,A_{R_i} ALi,ALi+1,…,ARi 是否互不相同。
我们非常简单就能想到一个暴力做法,下标从 L i L_i Li 到 R i R_i Ri 便利一下,直接统计一下是否互不相同,但时间复杂度直接上天: O ( n Q ) O(nQ) O(nQ)
于是我们考虑另一个方法,移动端点:
我们用一个
l
l
l 和
r
r
r 来计算的是下标从
l
l
l 到
r
r
r 中
a
i
a_i
ai 不相等的个数,这样判断一下
a
n
s
ans
ans 是否等于
r
−
l
+
1
r-l+1
r−l+1 就行了。
如图:
线段上的数字表示一个下标,如果我们当前处理的询问是
(
2
,
3
)
(2,3)
(2,3),下一个询问是
(
3
,
5
)
(3,5)
(3,5),那么我们要让
l
+
+
,
r
+
+
,
r
+
+
l++,r++,r++
l++,r++,r++,如图:
蓝色为原来的维护区间,绿色为变化情况,黄色为新的维护的区间。
但是我们移动端点的复杂度虽然可以
O
(
1
)
O(1)
O(1) 维护,但需要移动
n
n
n 次,时间复杂度还是
O
(
n
Q
)
O(nQ)
O(nQ)。
但我们可以想到如果将每一次的移动限定在一个范围之内,时间复杂度就将大为降低。
于是神奇的莫队算法就出现了,但其本质就是优美的暴力。
原理
核心
莫队最核心的部分还是它的排序。
我们把所有的元素分成多个块(即分块),把询问按以下方法排序:
如果左端点所在块的编号不同就按左端点从小到大排序,否则按右端点从小到大排序。
为什么要怎么排序呢,这样排序的话在每一个块内,
r
r
r 都可以一路从左扫到右,忽略不计,最坏情况下
l
l
l 则在块的长度内来回横跳,时间复杂度为
O
(
块的长度
×
询问次数
)
O(块的长度 \times 询问次数)
O(块的长度×询问次数).
可以知道,当块的长度为
n
\sqrt{n}
n 时跑的很快,那么总的时间复杂度就是
(
n
n
)
(n \sqrt{n})
(nn)。
维护
一般情况下,我们需要写两个函数来维护增加端点和删除端点两个操作。
为了方便我们用
l
,
r
l,r
l,r 表示当前维护区间,
L
,
R
L,R
L,R 表示需要维护的区间。
我们就从本题讲起:
del函数:
如果删除了一个点,这个区间内这个点的出现次数为0了,那么
a
n
s
=
a
n
s
−
1
ans=ans-1
ans=ans−1。
因为删除了这个数,这个区间内就没有这个数了。
如果
l
<
L
l<L
l<L,我们就要将
l
l
l 往右挪一下,及将
l
l
l 这个点删除,再将
l
l
l 更新为
l
+
1
l+1
l+1。
如果
r
>
R
r>R
r>R,我们就要将
r
r
r 往左挪一下,及将
r
r
r 这个点删除,再将
r
r
r 更新为
r
−
1
r-1
r−1。
代码如下:
void del(int x)
{
ans-=(--cnt[a[x]]==0);
}
while(l<L)del(l++);
while(r>R)del(r--);
add函数:
如果加入了一个点,这个区间内这个点的出现次数为1了,那么
a
n
s
=
a
n
s
+
1
ans=ans+1
ans=ans+1。
因为加入了这个数,这个区间内就多出了一个没有出现过的数,及前面的区间中并没有这一个数。
如果
l
>
L
l>L
l>L,我们就要将
l
l
l 往左挪一下,及将
l
−
1
l-1
l−1 这个点加入,再将
l
l
l 更新为
l
−
1
l-1
l−1。
如果
r
<
R
r<R
r<R,我们就要将
r
r
r 往右挪一下,及将
r
+
1
r+1
r+1 这个点加入,再将
r
r
r 更新为
r
+
1
r+1
r+1。
注:这里是加入,及原来的这个端点还在维护范围内,所以我们加入的应该是旁边的端点,及先更新再加入,而不是先加入再更新,这一点和删除并不一样
代码如下:
void add(int x)
{
ans+=(++cnt[a[x]]==1);
}
while(l>L)add(--l);
while(r<R)add(++r);
有了这两个函数之后,我们就可以不断维护每一个询问的区间了:
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+1;
int n,m,CL,CR,b[N],kn,ans[N],sum,a[N];
struct fy
{
int l,r,k;
}C[N];
bool cmp(fy x,fy y)
{
return (x.l/kn)==(y.l/kn)?x.r<y.r:x.l<y.l;
}
void add(int x)
{
if(++b[a[x]]==1)
sum++;
}
void del(int x)
{
if(--b[a[x]]==0)
sum--;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(NULL),cout.tie(NULL);
cin>>n>>m;
kn=sqrt(n);
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=m;i++)
cin>>C[i].l>>C[i].r,C[i].k=i;
sort(C+1,C+m+1,cmp);
for(int i=1;i<=m;i++)
{
int L=C[i].l,R=C[i].r;
while(CL<L)del(CL++);
while(CR>R)del(CR--);
while(CL>L)add(--CL);
while(CR<R)add(++CR);
if(sum==(R-L+1))
ans[C[i].k]=1;
}
for(int i=1;i<=m;i++)
cout<<((ans[i])?"Yes\n":"No\n");
}