什么是莫队算法
我们有时遇到一些询问不满足区间加减法,不能用线段树等数据结构维护的问题(如区间众数问题)。可以采用离线的方式处理询问,减少需要更改的次数,这就是莫队算法的思想。
莫队算法其实是对分块操作的一个升华~
先将长度为n的区间分成sqrt(n)个长度为sqrt(n)的块,然后将询问排序:第一关键字是左端点所在块的编号,第二关键字是右端点大小。这样对于左端点在同一个块里的询问,它们的右端点一定是递增的。我们通过更改左右端点的位置来更新所求值。更改一次左端点是O(√(n))的,左端点在一个块里的询问更改右端点是O(n)的。
莫队如何进行修改
莫队算法是离线算法,所以对于这些询问,它们所进行完的修改和未进行的修改都是互相不同的。所以我们在记录询问操作的每个区间的时候再记录一下这个询问操作是在哪个修改操作之后的,每次执行查询操作前都执行在它之前的修改,或将在它之后的修改操作中已执行的取消;这样就可以不改变原始的序列了。
【模板】带修改莫队 洛谷P1903
代码
#include<bits/stdc++.h>
using namespace std;
int n,m,blocksize,Left=1,Right=0,an=0;
int color[50010],num[1000010],pre[50010],ans[200010];
bool inside[50010];
struct Ques{
int L,R,num,lastR;
bool operator<(const Ques &r)const{return L/blocksize==r.L/blocksize?R<r.R:L<r.L;}
}q[200010];int nq=0;
struct Rep{
int p,Col,pre;
}r[200010];int nr=0;
void update(int x)
{
if(inside[x])
{
if(--num[color[x]]==0) --an;
}
else
{
if(++num[color[x]]==1) ++an;
}
inside[x]=!inside[x];
}
void change(int x,int Col)
{
if(inside[x])
{
update(x);
color[x]=Col;
update(x);
}
else color[x]=Col;
}
int main()
{
scanf("%d%d",&n,&m);
blocksize=sqrt(n);
for(int i=1;i<=n;++i)
{
scanf("%d",&color[i]);
pre[i]=color[i];
}
for(int i=1;i<=m;++i)
{
char c;
scanf("%s",&c);
if(c=='R')
{
++nr;
scanf("%d%d",&r[nr].p,&r[nr].Col);
r[nr].pre=pre[r[nr].p];
pre[r[nr].p]=r[nr].Col;
}
else
{
++nq;
scanf("%d%d",&q[nq].L,&q[nq].R);
q[nq].num=nq;
q[nq].lastR=nr;
}
}
sort(q+1,q+nq+1);
for(int i=1;i<=nq;++i)
{
for(int j=q[i-1].lastR+1;j<=q[i].lastR;++j) change(r[j].p,r[j].Col);
for(int j=q[i-1].lastR;j>=q[i].lastR+1;--j) change(r[j].p,r[j].pre);
int l=q[i].L,r=q[i].R;
while(Left<l) update(Left++);
while(Left>l) update(--Left);
while(Right<r) update(++Right);
while(Right>r) update(Right--);
ans[q[i].num]=an;
}
for(int i=1;i<=nq;++i) printf("%d\n",ans[i]);
return 0;
}