简介
莫队是什么
莫队算法是由莫涛提出的算法。在莫涛提出莫队算法之前,莫队算法已经在 Codeforces 的高手圈里小范围流传,但是莫涛是第一个对莫队算法进行详细归纳总结的人。莫涛提出莫队算法时,只分析了普通莫队算法,但是经过 OIer 和 ACMer 的集体智慧改造,莫队有了多种扩展版本。(来自oiwiki)
莫队是干什么用的
莫队是一种可以处理序列上问题的离线算法
它的适用性很强,强到可以解决绝大多数无修改的不强制在线的序列上查询问题
总之,这是一种极为优美的暴力
为什么选择莫队
第一个原因我们在上面已经提到了,是因为适用性强,暂且不表
第二个原因就是因为它真的够快,O(n√n)对于大多数想让你AC的题目来说已经足够快了
普通莫队
原理
显然对于一个区间[l,r]我们可以O(1)暴力扩展到[l+1,r]或[l-1,r]或[l,r+1]或[l,r-1],那么我们在将数据离线后以l为第一关键字、r为第二关键字排序,按顺序暴力从上一次询问转移到下一次询问,对于所有询问我们可以在O(n√n)的复杂度下解决
时间复杂度证明
现在我们设有q个询问,区间长度为n,块长度为len,则对于所有块在最坏的情况下时间复杂度为O(qlen+n2/len)
其中len的大小是我们需要确定的
利用均值不等式,qlen+n2/len<=2√q*n2=2n√q
那么对于时间复杂度则为O(n√q)
证毕
模板
void add(int i){/*update ans*/}
void del(int i){/*update ans*/}
void solve(){
sort(a+1,a+m+1);
for(int i=1,l=1,r=0;i<=m;i++){
if(a[i].l==a[i].r){ans[a[i].id]=0;continue;}
while(l>a[i].l)add(q[--l]);
while(r<a[i].r)add(q[++r]);
while(l<a[i].l)del(q[l++]);
while(r>a[i].r)del(q[r--]);
//update ans;
}
}
小Z的袜子
思路
这是一道莫队模板题,每进行一次操作时,统计当前块中相同个数的数量以及当前数,概率可分别计算分子和分母,分子为总数,分母为(r-l+1)*(r-l)/2
代码
#include<bits/stdc++.h>
#define N 50005
#define ll long long
using namespace std;
inline ll read(){
ll x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}
ll ans1[N],ans2[N],cnt[N],q[N],maxn,sum;
struct query{
ll l,r,id;
bool operator<(const query &x)const{
if(l/maxn!=x.l/maxn)return l<x.l;
return (l/maxn)&1?r<x.r:r>x.r;
}
}a[N];
inline void add(int i){sum+=cnt[i];cnt[i]++;}
inline void del(int i){cnt[i]--;sum-=cnt[i];}
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
int main(){
int n=read(),m=read();maxn=sqrt(n);
for(int i=1;i<=n;i++)q[i]=read();
for(int i=1;i<=m;i++){ll l=read(),r=read(),id=i;a[i]=(query){l,r,id};}
sort(a+1,a+1+m);
for(int i=1,l=1,r=0;i<=m;i++){
if(a[i].l==a[i].r){ans1[a[i].id]=0;ans2[a[i].id]=1;continue;}
while(l>a[i].l)add(q[--l]);
while(r<a[i].r)add(q[++r]);
while(l<a[i].l)del(q[l++]);
while(r>a[i].r)del(q[r--]);
ans1[a[i].id]=sum;ans2[a[i].id]=(ll)(r-l+1)*(r-l)/2;
}
for(int i=1;i<=m;i++){
if(ans1[i]){ll g=gcd(ans1[i],ans2[i]);ans1[i]/=g,ans2[i]/=g;}
else ans2[i]=1;
printf("%lld/%lld\n",ans1[i],ans2[i]);
}
return 0;
}
易犯错误
由于之前我们已经对序列排好序了,所以这里我们应该使用原编号进行查询,而不是排序后的序号