莫队与分块一样,都是一种优化时间复杂度的思想方法,它的应用十分广。
莫队分三种:普通莫队、树上莫队和带修莫队。
-
普通莫队
莫队的实质就是通过分块和排序来提高效率。
分块就不讲了。还是和平常一样块大小为,块的个数也是。
关键的是排序。排序方式如下:同一块的按r从小到大排,不同块的按l从小到大排。(其实就是先按块的顺序排,同一块的再按r排)。
贴一下代码,方便理解:
int cmp(const data &a,const data &b)
{
if(a.be==b.be)
{
if(a.r<b.r)return 1;
else return 0;
}
else
{
if(a.l<b.l)return 1;
else return 0;
}
}
排好序之后,我们这样来求答案:对于每一块的开头,我们暴力求出l~r的答案。然后对于同块的接下来的l、r,我们暴力移动左右端点更新答案就好了。
到这里我们来分析一下时间复杂度,这里我们分类讨论一下:
- 首先对于每一块开头暴力求l~r的答案,每一次求最多是O(n)的,因为最多有快,所以这个的时间复杂度是O(n)的。
- 然后就是暴力移动r,因为同一块的r是已经排好序了,所以同一块中r最多移动n次,而一共有块,所以这个的时间复杂度也是O(n)的。
- 最后是每次移动l,这个就很显然了。因为同一块中每次移动l最多移动距离,而最多移动次,所以时间复杂度是O(n)的。
到这里,我们就可以得出结论了:莫队的时间复杂度不是O()的,而是O(n)的。这是一个优秀的做法。
-
树上莫队
如果我们现在要查询树上的一条链的答案,那么该怎么做呢?
首先我们要知道一个叫欧拉序的东西(也叫括号序)。
我们dfs一遍,每次dfs到一个点时就把这个点加入队列,dfs完这个点的子树时又把它加入队列。dfs完整棵树后我们就得到了一个长度为2n的队列。
然后记st[i]表示i点第一次出现的位置,ed[i]表示i点第二次出现的位置。接着我们就可以发现对于一组x、y(假设st[x]<=st[y]):
- 若x为lca,那么st[x]~st[y]中只出现一次的点就是我们要求的链
- 若x不为lca(y也不可能为lca),那么ed[x]~st[y]中只出现一次的点就是我们要求的链
这个画几个例子就明白了。不过当我们画出例子之后,就会发现我们这样做漏掉了一个点,这个点就是lca。不过没关系,最后补上lca就行了。
-
带修莫队
带修莫队就是再加入一个tim作为第三关键字排序(当l和r都在同一块是才排tim)。tim表示的是当前询问实在第tim个修改结束之后才求答案的。排好序之后我们不单止要暴力移动l和r,还要暴力移动tim,这样就可以了。
时间复杂度:最坏情况O()。
分析如下:我们这时只需要考虑tim的移动。和上面一样,只有当l、r都有序时tim的移动次数才是n的。在最坏情况下,假设每一个l后所有的块都被r占据了,那么总共l、r有序的有(n/L)^2个。每一个tim移动n次,那么总的时间复杂度就是。当时最小,最小为。