这是本文档旧的修订版!
莫队实际上是“优雅的暴力”,主要应用于给定一段连续的序列,让你求出序列中每个数出现的次数或者处理和数出现次数有关的问题。一般来说,我们选择建一个数组cnt[i],从头到尾扫一边序列,遇到一个数j,就让cnt[j]++,不过这样,复杂度会很高,多次查询,就会让复杂度飙升至O(n2),所以对于多次查询我们一般改变策略,用两个指针i,j在数列上移动,来缩减目标范围,新遇到加入,已在数列里面就删除,只要对cnt数组进行操作。复杂度的到一定的优化,然而,对于查询区间为[1,2][2,3][3,4]……这样和重新扫没区别,一样爆炸…… 所以就有了莫队的分块的做法。 我们考虑将序列分成sqrt(n)取整块,然后将查询区间按照起始点 l 所属块的大小排序,然后对于起点所属块相同的点,就按照终点从小到大排序,然后离线做。这样块最多移动sqrt(n)次,在每一个块中重点最多移动n次,这样最终复杂度为nsqrt(n)。
最后放一个模板 luogu2709小b的询问
#include <bits/stdc++.h> using namespace std; const int maxn=5e4+13; struct node{ int l,r; int num; }tt[maxn]; int n,m,k,ans=0; int bel[maxn]; int a[maxn]; int cnt[maxn];//村每个数出现的次数 int end[maxn];//存答案 bool cmp(node a,node b) { return bel[a.l]==bel[b.l]?(bel[a.l]&1?a.r<b.r:a.r>b.r):bel[a.l]<bel[b.l]; } #define add(x) ans+=!cnt[x]++ #define del(x) ans-=!--cnt[x] //提速法 int main() { int l,r; scanf("%d%d%d",&n,&m,&k); int num=(int)sqrt((double)(n)); int x=ceil((double)(n)/num); for(int i=1;i<=x;i++) { for(int j=(i-1)*num+1;j<=min(n,i*num);j++) { bel[j]=i; } } for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=m;i++) { scanf("%d%d",&l,&r); tt[i]=(node){l,r,i}; } sort(tt+1,tt+1+m,cmp);//分块 l=1,r=0; for(int i=1;i<=m;i++)//莫队四件套 { while(r<tt[i].r) add(a[++r]); while(r>tt[i].r) del(a[r--]); while(l>tt[i].l) add(a[--l]); while(l<tt[i].l) del(a[l++]); end[tt[i].num]=ans; } for(int i=1;i<=m;i++) printf("%d\n",end[i]); }