轻松易懂的莫队算法

莫队算法是一个可以离线处理区间信息的算法,通过离线+分块的方法将区间问题高效处理,在OI竞赛中十分常用。

一、基本思想

已经说过了,基本思想就是离线加分块。那么我们该如何通过莫队算法处理离线的询问呢?

对于一个我们已经处理好信息的区间 \([l,r]\) ,假如我们可以逐步移动左右端点更新信息,那么一个新区间 \([l',r']\) 的信息就可以通过若干次移动左右端点处理出来。

单次移动更新的代码示意:

while(L<l[i]) erase(a[L++]);
while(L>l[i]) add(a[--L]);
while(R<r[i]) add(a[++R]);
while(R>r[i]) erase(a[R--]);
//L,R为当前已经处理好的区间,l[i],r[i]为某一次的询问。
//在拓展L,R左右端点的同时,增加/删除信息。

为了可视化这一过程,我们将区间转化为平面内的一个点,用横坐标表示左端点,纵坐标表示区间右端点。区间的更新可以看作为平面内一个点的移动,从一个点移动到另一个点的代价为这两点的曼哈顿距离。

假如我们有若干个待处理区间,那么在平面内就是:

geogebra-export2.png
我们要处理所有的区间,也就是要找到一条路径把所有点串起来。我们就要考虑如何规划移动路线使得移动的代价尽量小。

这样,我们可以考虑按照点的横坐标均分成若干块,

geogebra-export3.png

我们按照次序依次处理每个块内的点,对于每个块的点,我们按照纵坐标从小到大处理。也就是,不同块按横坐标排序,同一块按纵坐标升序排序

bool cmp(int i,int j){
    if(l[i]/k==l[j]/k) return r[i]<r[j];
    return l[i]<l[j];
}//k是块的大小。

将这个过程可视化,就是这样的:

geogebra-export4.png

看上去不错,但是这样做的效率如何?又该怎么分块合适?

我们不妨设横坐标分块的大小为 \(k\) ,序列长度为 \(n\) ,有 \(m\) 个询问。分别考虑横坐标和纵坐标移动的时间复杂度。

对横坐标而言,我们已经分好了块,于是两个询问之间横坐标的移动复杂度一定不超过 \(O(k)\) 。故移动横坐标的复杂度为 \(O(mk)\)

至于纵坐标的移动,单块内移动复杂度不会超过 \(O(n)\) ,所以纵坐标移动的复杂度为 \(O(n\frac{n}{k})=O(\frac{n^2}{k})\)

于是莫队算法总的时间复杂度为

\[O(mk)+O(\frac{n^2}{k})=O(mk+\frac{n^2}{k})\geq O(\sqrt{n^2m})=O(n\sqrt m) \]

根据均值不等式,当且仅当 \(mk=\frac{n^2}{k}\)\(k=\frac{n}{\sqrt m}\) 时等号成立。一般来说 \(n\)\(m\) 同阶,所以块的大小取 \(\sqrt n\) 时,莫队算法复杂度为 \(O(n \sqrt n)\)。这个复杂度在大多数情况下相当优秀。

此外普通莫队还有一个小优化:对于奇数块询问按右端点升序排序,偶数块按右端点降序排序。结合示意图,应该能明白其背后原理。

这只是莫队的最基本思想。在 OI 界,莫队算法随着 OIer 们的探索不断衍生出各种变体。接下来我们来看看莫队算法有哪些强力的拓展版本吧。

二、衍生算法

(1)带修莫队

来看一个例题:

有一个序列,\(m\) 个操作,包括:

  1. 修改序列上某一位的数字
  2. 询问区间 \([l,r]\) 中不同数字的个数。

这个和普通莫队相比,多了修改的操作。莫队算法该如何处理这样的修改询问呢?

啊其实很简单,这个修改其实是随着时间进行的,那么我们只要对修改和询问标记上一个时间维度,在询问间移动时同时移动时间,更新时间信息。

这样就有三个维度了,一个询问的信息为 \((l,r,t)\) 。我们以左端点为横坐标,右端点为纵坐标,时间为竖坐标。同样地,我们对三维空间中的点进行分块:

bool cmp(int i,int j){
    if(l[i]/k!=l[j]/k) return l[i]<l[j];
    if(r[i]/k!=r[j]/k) return r[i]<r[j];
    return t[i]<t[j];
}//l,r,t分别为左端点、右端点与时间。k是分块大小。

这样分块起到了一个什么效果?我们把 \(xOy\) 面分为了若干个大小为 \(k^2\) 的块,整个空间被我们分为了若干个底面面积为 \(k^2\) 的柱体。我们以横坐标为第一关键字、纵坐标为第二关键字处理这些空间块。对于每个柱体,按竖坐标从小到大处理每个点。

分析一下这个算法的复杂度:

为了简便计算,序列长度为 \(n\)、询问个数记为 \(m\) ,时间记为 \(t\) ,分块大小为 \(k\)

横坐标的移动:\(O(mk)\)

纵坐标的移动:\(O(k\frac{n^2}{k^2}+mk)=O(\frac{n^2}{k}+mk)\)

竖坐标的移动:\(O(\frac{n^2t}{k^2})\)

我们来分析一下如何分块:记 \(k=n^\alpha\),认为 \(n\)\(m\)\(t\) 同阶,均为 \(n\)

总时间复杂度为:

\[O(n^{1+\alpha})+O(n^{2-\alpha})+O(n^{3-2\alpha})=O(n^{\max\{1+\alpha,2-\alpha,3-2\alpha\}}) \]

\(\max\{1+\alpha,2-\alpha,3-2\alpha\}\) 最小时,\(\alpha=\frac{2}{3}\)。故分块大小应为 \(\sqrt[3]{n^2}\)。此时时间复杂度为 \(O(n\sqrt[3]{n^2})\)

(2)树上莫队

普通莫队只能处理序列上的区间问题。假如把问题放到树上,那么还怎么解决呢?

给出一棵节点数为 \(n\) 的树。每个点有一个颜色 \(a_i\) ,给出 \(m\) 个询问,求 \(u\)\(v\) 两点路径上有多少种不同的颜色。

很自然地,我们会想把一棵树转变为一个序列,以便莫队算法处理。

tree.png

这是一棵树,我们可以用欧拉序将其转变为一个序列。欧拉序就是从根结点出发,按深度优先搜索的顺序时经过所有点的顺序。与dfs序不同,一个点会分别在进入和退出时加入序列。

比如上面这棵树,它的dfs序为1 2 3 4 5 6 7

欧拉序却为1 2 3 3 4 5 5 6 6 4 2 7 7 1

将结点 \(u\) 的进入、退出时间分别记为 \(in_u\)\(out_u\) 。我们可以发现任意两点之间的路径只有两种情况(下文的讨论保证保证 \(u\)\(v\) 先被遍历):

1、\(u,v\) 为祖先/子孙关系。那么 \(u\) \(v\) 之间的路径是一条自上而下的链。以从 \(1\)\(4\) 的路径为例,我们观察一下 \(in_1\)\(in4\) 之间的欧拉序:1 2 3 3 4。发现 \(3\) 出现了两次,因为 \(3\) 并不在路径上,中途退出了。对于这种情况,我们处理 \([in_u,in_v]\) 这个区间的信息即可。

2、\(u\)\(v\) 在同一棵子树里。那么它们之间的路径会经过 \(lca(u,v)\)。以 \(3\)\(6\) 为例,观察 \(out_3\)\(in_6\) 之间的欧拉序:3 4 5 5 6\(5\) 不在路径上,所以出现了两次。但有没有发现少了点啥?\(lca(3,6)\) 也就是 \(2\) 不见惹。所以处理询问时要把 \(lca(u,v)\) 的信息加上。

综合以上两种情况,我们发现,对于已经询问了的区间,假如新加入区间的点已经被统计过,那么将该点的信息去掉;反之加入信息。我们可以这样写 update 函数:

void update(int u) {
	int x=lst[u];
	vis[x]?erase(c[x]):add(c[x]);
	vis[x]^=1;
}//lst是欧拉序。vis记录某个点是否出现过。

其它和普通莫队没什么区别了。

(3)回滚莫队

上述问题都有着区间可自由拓展的特性,但有些问题让区间只能单向操作。

来看这道题:

给出一个长度为 \(n\) 的序列,有 \(m\) 个询问,每次询问一个区间 \([l,r]\) ,求其中出现次数最多的数字个数。

容易发现,当区间向外拓展时,答案是很容易维护的;然而区间向内回退时却不能更新答案。这要求莫队在运行时只能 add 不能 erase。但这个难不倒 OIer 们,他们设计出了如下算法:

1、首先将所有询问按照左端点分块、右端点升序排序(同普通莫队)。假如 \(l,r\) 在同一个块内那就单独拎出来暴力算,因为怎么样也不会超过 \(O(k)\)

2、假如一个新的询问的左端点在不同的块内了(也就是左端点在上一个块内的询问已经处理完),假如新的块为 \(A\),我们将左端点 \(L\) 重置为 \(A\) 的右端点加 \(1\) ,右端点 \(R\) 设置为 \(A\) 的右端点,并清除区间信息。此时区间为空。

3、右端点是升序,可以无脑拓展。右端点拓展出的信息要单独维护。每次询问之前,我们先把左端点重置为 \(A\) 的右端点加 \(1\) 并清空左端点拓展的信息,再作更新,回答询问。这就是回滚

回滚莫队.png

可以发现,每次询问后左端点都会回到块的右端。看样子多了很多操作,但仔细思考一下,其复杂度与普通莫队是一致的。分块大小也与普通莫队一致。所以回滚莫队复杂度依然为 \(O(n\sqrt n)\)

三、强制在线

在线是不可能在线的,这辈子都不可能在线的。


感谢阅读!

posted @ 2021-12-18 21:12  HyperSQ  阅读(86)  评论(0)    收藏  举报