莫队算法是由之前的国家队队长莫涛发明,故称为莫队算法。其用于处于静态区间查询。
对于区间查询,我们一般会使用主席树或树套树之类吊炸天的数据结构来进行处理。这是基于我们可以对区间进行二分,并通过分治的方式保证拥有对数级别的性能。当然前提是查询的内容要适合进行分治处理,对于形如最大值最小值,分治非常适合,但是也有非常不适合的,譬如我们要查询区间中重复次数最多的数值,这时候分治就失去了意义。这时候莫队算法就站了出来。
对于上面的问题,如果在已知[l,r]上每个数字的重复次数,那么对于[l,r+1],[l,r-1],[l+1,r],[l-1,r]上的询问,可以在O(1)的时间复杂度内解决。直接按序执行查询,其时间复杂度将可能达到O(mn),其中n为区间长度,m为查询数目。莫队算法则需要对查询进行重新组织。定义两个查询[a,b]与[x,y]的距离为|a-x|+|b-y|,即二者的曼哈顿距离。如果我们将每个查询视作一个顶点,而两个查询之间的距离视作两个结点间的边长,将查询[0,0]视作起点,那么要发挥莫队算法的完全性能,我们需要找到从起点出发的一条经过所有顶点的最短路径。但是要找这样的一条路径显然是费事费力的,因此我们会选择另外一种方法-分块,降低莫队算法的时间复杂度。分块是这样的,我们将[1,n]均分为n/k个长度为k的块。之后我们对查询进行排序,排序的第一关键字是查询左边界所处的块的编号,而第二关键字是查询右边界。之后我们按排序好的结果逐一执行查询请求。
时间复杂度的证明如下:
对于每一个块,由于左边界落于该块中的查询的右边界递增,故右边界最多移动n次。由于总共有n/k个块,故最多移动n^2/k次。
对于每一个块,由于左边界落于该块中的查询切换时,左边界最多移动k次,而总共切换最多发生m次,因此左边界最多移动km次。
在一个块处理完进入另外一个块的时候,我们可能需要完全重置当前维护的区间的信息,即左右边界最多加总移动2n次,由于最多发生n/k次,故最多移动2n^2/k次。
综合上面可知总的时间复杂度为O(nlog2(n))+O(km)+O(n^2/k)=O(nlog2(n)+km+n^2/k)。当m很小的时候,我们完全可以选择k为n,这时候时间复杂度为O(nlog2(n)),而当m较大时,我们可以选择k为1,此时时间复杂度为O(n^2+m)。下面是理论上应该选取的最优k值,仅用到简单的微分技巧:
$$ f\left(k\right)=n\log_2n+km+n^2/k $$ $$ f‘\left(k\right)=m-n^2/k^2 $$ $$ f‘\left(k\right)=0\Rightarrow mk^2=n^2\Rightarrow k=n/\sqrt{m} $$
此时的时间复杂度为$ O\left(n\cdot\left(\log_2n+\sqrt{m}\right)\right) $。