莫队算法(离线)

何谓莫队算法

莫队算法是莫涛队长发明的,为表示对他的尊敬,故称这种算法叫莫队。


适用范围

一种处理序列操作的离线算法,适用范围广,复杂度一般带根号。


莫队算法的思路

假设题目不涉及修改操作。

将所有操作离线,将所有操作进行二元组排序,第一维是左端点所在块的编号,第二维是右端点。

排序后,按照顺序处理询问,维护一个双端队列,同时维护队列内区间的答案,每次从$[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)$之间的曼哈顿距离即为两个询问间转移的代价。
我们要做的就是通过调整顺序,最小化询问间转移的代价之和。
最优的计算顺序即为这些点的曼哈顿最小生成树,然而求解这东西非常麻烦,于是我们采取直接排序这一更为暴力的方法,获得时间复杂度和代码复杂度的平衡。


什么时候用莫队算法

当题目要求的操作较为复杂,限制性强,用线段树等数据结构不容易维护信息,或者维护信息复杂度较高,且题目对于时间的要求较为宽松时,支持离线,可以考虑使用莫队。
时间复杂度一般带根号,带修改莫队复杂度更高,且常数大,如果可以直接线段树或者分块干掉的题目,尽量不要使用莫队,有些题跑的真的很慢(即使你算出的时间复杂度非常优秀)。

适用条件:

  在序列上由$[L,R]$的答案伸缩左右端点时,仅为$\Theta (1)$的复杂度。某些$\Theta (\log n)$转移答案的强行用莫队做,甚至不如$\Theta O(n^2)$暴力跑得快。


注意

  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++

posted @ 2019-07-24 20:33  HEOI-动动  阅读(440)  评论(0编辑  收藏  举报