莫队算法是莫涛队长发明的,为表示对他的尊敬,故称这种算法叫莫队。算法
一种处理序列操做的离线算法,适用范围广,复杂度通常带根号。数据结构
假设题目不涉及修改操做。spa
将全部操做离线,将全部操做进行二元组排序,第一维是左端点所在块的编号,第二维是右端点。blog
排序后,按照顺序处理询问,维护一个双端队列,同时维护队列内区间的答案,每次从$[L,R]$的答案,扩展到$[L-1,R]$ $[L,R-1]$ $[L+1,R]$ $[L,R+1]$的答案,最终获得当此询问区间的答案。排序
假设序列长度为$n$,询问数为$m$,分块块长为$d$,那么左端点在同一块内时,左端点移动每次不超过$d$,右端点总共移动不超过$n$,显然是$\Theta (m\times d+\frac{n^2}{d})$,令$d=\sqrt{n}$,$n,m$同阶,则时间复杂度为$\Theta(n\sqrt{n})$,常数很大。队列
莫队算法的精髓在于,离线的状况下,经过调整询问与修改间的顺序,由已知的答案扩展到未知的答案,以优秀的计算顺序换取优秀的时间复杂度。
每一个询问$[L,R]$能够看做平面上的整点$(L,R)$,和另外一个询问$(l,r)$之间的曼哈顿距离即为两个询问间转移的代价。
咱们要作的就是经过调整顺序,最小化询问间转移的代价之和。
最优的计算顺序即为这些点的曼哈顿最小生成树,然而求解这东西很是麻烦,因而咱们采起直接排序这一更为暴力的方法,得到时间复杂度和代码复杂度的平衡。class
当题目要求的操做较为复杂,限制性强,用线段树等数据结构不容易维护信息,或者维护信息复杂度较高,且题目对于时间的要求较为宽松时,支持离线,能够考虑使用莫队。
时间复杂度通常带根号,带修改莫队复杂度更高,且常数大,若是能够直接线段树或者分块干掉的题目,尽可能不要使用莫队,有些题跑的真的很慢(即便你算出的时间复杂度很是优秀)。扩展
适用条件:方法
在序列上由$[L,R]$的答案伸缩左右端点时,仅为$\Theta (1)$的复杂度。某些$\Theta (\log n)$转移答案的强行用莫队作,甚至不如$\Theta O(n^2)$暴力跑得快。im
1.++和--是在前仍是在后。
2.通常状况下四种操做的顺序无所谓,可是有的题是有影响的,比方说:BZOJ4358:permu。
普通莫队:
struct rec
{
int id;
int l;
int r;
int pos;
}q[N];
int a[N];
int ans[N];
bool cmp(rec a,rec b){return a.pos==b.pos?a.r<b.r:a.pos<b.pos;}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int t=sqrt(n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
q[i].pos=(q[i].l-1)/t+1;
}
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++)
{
while(l>q[i].l)upd(--l,1);
while(r<q[i].r)upd(++r,1);
while(l<q[i].l)upd(l++,0);
while(r>q[i].r)upd(r--,0);
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}
奇偶莫队:
bool cmp(rec a,rec b){return (a.pos)^(b.pos)?a.l<b.l:(((a.pos)&1)?a.r<b.r:a.r>b.r);}
rp++