*莫队总结
前言
莫队是处理可离线区间问题的利器,其优势在于每次的修改都是单点修改。
同样的,这样的弊端就是只能处理离线问题,而且时间复杂度比较高,是根号级别的。
同时,莫队也是最小曼哈顿距离生成树的一个近似解。
在数据范围接近根号级别且是限定区间来询问,同时可以离线的问题,我们就可以考虑使用莫队解决。(在线莫队要求的是空间的根号级别)
正文
(带 * 的题目表示这道题在其它地方有重合,这里可以不用看,比如一道带修树上莫队,假设其会出现很多次,那么只有不带 * 那次时是读者已经可以做的了,如果你是读者并从前往后依次看的话/fad)
普通莫队
基础莫队
这是最基础的莫队,仅需要掌握莫队的基本写法即可,同时可以学习一些常数优化(比如奇偶块排序、直接写道循环里而不写成函数,简单的函数加\(inline\)等等)。
例题:
莫队区间数颜色最经典的题目。
同上,还是模板题。
区间数颜色经典问题,可以说是莫队模板题,只不过需要小小的转化。
区间中出现了偶数次的数有多少个,同样也是直接开桶存一下即可。
本质还是区间数数的个数,只是需要把每次单点修改的影响转化一下。
给定一个 01 序列,每次询问一个区间有多少个子区间其 1 的个数比 0 正好多 k 个,k 最初给定。
我们可以做一遍前缀和,询问就相当于是在问两个前缀和的差等于 k 的有序对个数。
于是我们每次单点修改的时候询问一下当前值 \(sum+k\) 或者 \(sum-k\) 这个桶个数即可,用哈希维护桶。
CF220B Little Elephant and Array
每次询问当前出现次数等于自己的值,也就是开一个桶记录出现次数再同时判断即可。
维护区间众数出现次数,直接莫队,然后开两个桶一个记录值为 \(x\) 的有多少个数,一个记录有 \(x\) 个数的桶有多少个。
这类问题如果再加上维护众数具体是谁的限制的话,可以加一个 \(unordered\)_\(set\) 来维护,手写办法暂时不知道。
可以转化成维护区间众数个数,做法同上。
询问一个区间的数是否互不相同,可以直接莫队来维护区间数颜色即可。
普通莫队+数据结构
这部分的题可能就不是最简单的那样直接开一个桶就能随便维护信息,而是需要用一些常见的复杂度较小的数据结构来维护。
例题:
CF351D Jeff and Removing Periods
观察性质后可以转化成维护:对于所有值,维护其位置集合当前是否是等差数列,可以使用双端队列以及并查集来做,具体见该题。
给定一个序列,询问区间内出现次数为 1 的数,输出任意一个。
可以直接值域分块,然后每次 \(O(1)\) 添加 \(O(\sqrt{n})\) 查询即可。
注意:莫队时常都可以和值域分块结合在一起,因为其 \(O(1)\) 插入/删除 和 \(O(\sqrt{n})\) 询问的特性和莫队正好契合。
直接莫队暴力维护,然后发现这个 \(mex\) 的级别一定是 \(O(\sqrt{n})\) 的,原因显然,所以每一次询问的时候暴力枚举 \(mex\) 即可。

集合求交,于是想到 \(bitset\) 维护,这里三个区间,考虑套路拆成分别三个 \(bitset\) 然后存起来,最后直接求交即可。
但是这道题卡了空间,我们把 \(bitset\) 直接存到答案数组里会炸掉,于是可以考虑把所有询问分块,每 \(B\) 个询问我们莫队处理一次,这样需要缓存的 \(bitset\) 就没有那么多了,省下了空间。
给定一个序列,多次询问区间内是否存在两个数 和/差/积 为 \(x\) 。(\(x\) 每次询问给出)。
首先莫队去掉区间限制,然后发现前面两个都可以考虑 \(bitset\) 和集合求交来维护,乘积的话可以发现一个数的因数个数是 \(\sqrt{n}\) 级别的,于是直接预处理值域中所有数的所有因数,然后每次就询问对应两个数桶里面有没有即可。
给定一个序列,多次询问区间内是否存在两个数 和/差/积/商(整除) 为 \(x\) 。(\(x\) 每次询问给出)。
上一题加强版,主要讨论除操作怎么处理。
我们发现这个商如果很大,那么这样的数对个数一定不会很多,这启示我们可以考虑根号分治,于是可以考虑对于这部分(也就是商 \(x\ge \sqrt{n}\) 的数)直接暴力枚举除数,然后判断被除数。
对于商比较小的询问,我们可以考虑另外维护,因为这样的商只有 \(1~\sqrt{n}\),于是可以考虑直接暴力枚举每一个数,然后离线所有询问,考虑对于 \([1,n]\) 每一个位置计算出当前点作为 \(r\) 的最右的端点 \(l\) 似的存在一对符合条件的数。
然后我们记 \(las[i]\) 表示值为 \(i\) 的最后出现的位置,\(res[i]\) 表示位置 \(i\) 在之前的最右端的可匹配到一对 \((a,a*x)\) 的左端点位置,两者的更新显然,于是就做完了。
给定一个序列,多次询问一个区间当中值域在 \([a,b]\) 当中的值的种类数。
双重限制数颜色,我们可以用莫队来处理序列上的限制,然后用值域分块来处理值域上的限制(因为这是值域限制,又是莫队,所以很好想到),然后就做完了。

直接每次现算似乎不太好做,于是考虑单独计算每个数对全局的贡献。
发现其实可以转化成 \(2^{r-l+1}-2^{r-l-k+1}\) 次(全局的减掉不出现的,剩下的就是出现的),其中 \(k\) 是这个数的出现次数。
然后我们可以根号平衡来做,对出现次数进行根号分治,然后小于的可以开两个 \(cnt\) 数组,意义分别是出现次数和该出现次数的所有数的和,然后对于大于的可以直接链表/\(unordered\)_\(set\)暴力维护即可。
注意这里还需要光速幂的维护。
莫队维护区间的推广
这部分要点是要理解“区间限制”的本质,这个所谓“区间”并不一定是真正的区间,也有可能是一组点对,本质上来说莫队可以自动维护一个两层的限制。
同时一些题目的条件如果有多个区间的限制组合,可以思考是否能够拆分成单独的询问,可以尝试前缀和相减的思路。
例题:
给定一个序列,询问区间内出现次数为 1 的数,输出任意一个。
很容易想到莫队,然后可以用一个 \(unordered_set\) 来维护,但是跑不过,于是我们可以考虑用一个栈来维护,也就是可以直接放到栈里,然后添加直接加,删除的话我们每次进去的时候会记录位置,直接把当前栈顶移动过去即可(似乎叫做懒惰删除?)。
给定一个字符串,每次询问其一个子区间有多少个子区间重排后可以构成回文串。
首先因为只需要奇偶性于是可以考虑状压,然后发现每次的修改就是相当于增加了一堆 前缀/后缀 区间,于是我们考虑直接来维护这些区间的信息。
在这里就是拿一个桶来存当前 前/后缀 的每一个值的个数,查询因为最多可以有一个奇数,于是直接查当前数异或 \((1<<i)\) 的桶里面的值即可,复杂度是单次\(O(26)\)的。

直接维护似乎并不好做,于是考虑拆一下贡献,把答案改写成前缀区间相减的形式,然后可以拆开式子变成四个,发现就可以直接算了,把四个加起来即可。

集合求交,于是想到 \(bitset\) 维护,这里三个区间,考虑套路拆成分别三个 \(bitset\) 然后存起来,最后直接求交即可。
但是这道题卡了空间,我们把 \(bitset\) 直接存到答案数组里会炸掉,于是可以考虑把所有询问分块,每 \(B\) 个询问我们莫队处理一次,这样需要缓存的 \(bitset\) 就没有那么多了,省下了空间。
给定一个序列,每次询问:给定 \(l,r,x,y\) ,问 \([l,r]\) 有多少个子区间满足其区间所有数使得 \(x\leq a_i \leq y\)。
序列上莫队显然不太行,于是直接对于值域进行莫队,然后题目发现可以转化成 \(01\) 序列统计极长子区间算贡献,因为这里值和位置一一对应,所以相当于每次移动指针,只会有一个位置进行 \(01\) 变换。
然后考虑维护,显然 \(0->1\) 好维护但是 \(1->0\) 不好维护,于是这里可以使用回滚莫队避免删除,然后区间的限制,我们就可以直接使用值域分块来维护全局,每次临时询问即可,注意分块要撤销。
带修莫队
基础带修莫队
例题:
可以说是模板题,注意的就是带修莫队的基本实现问题。
同样是区间数颜色问题,但是要支持单点修改,也就是带修莫队模板题,交换的办法很妙。
SP30906 ADAUNIQ - Ada and Unique Vegetable
询问一个区间当中只出现一次的数的个数,支持单点修改,直接带修莫队维护区间颜色个数即可。
树上莫队
分成两种实现,一种是直接树分块,另外一种是欧拉环游序/\(dfn\) 序然后莫队。
树分块莫队注意对于 \(lca\) 处的讨论和排序方法。
基础树上莫队
注意树上莫队需要的排序方案是:先按块排,然后按照 \(dfn\) 序排第二维,正确性易证(也就是第二维最多绕整棵树两圈,第一维变化是 \(O(\sqrt{n})\) 的)。
例题:
SP10707 COT2 - Count on a tree II
树上路径数颜色,直接树分块然后普通莫队即可。
树上带修莫队
树上莫队+带修莫队,需要注意的就是这里我们可以直接两个端点都按照块排序,剩下的直接按照修改时间排序。
例题:
相当于维护树上数颜色带修改问题,直接树分块过后带修莫队维护即可。
SP32952 ADAFTBLL - Ada and Football
转化一下,本质还是带修的树上数颜色个数问题,树分块+带修莫队直接做。
树上带修莫队+值域分块,询问树上路径第 \(k\) 大,单点修改,首先不考虑询问就是普通带修树上莫队,现在有了询问,发现值域分块也可以维护第 \(k\) 大,于是就做完了。
树上莫队+数据结构
同样是莫队+数据结构,只不过搬到了树上,没什么区别。
例题:
树上询问一条路径上(给定)是否存在一个数 \(x\) 在 \([l,r]\) 中,且出现次数为 1 。
树上莫队后,难点就在于怎么维护这个 “\(x\) 在 \([l,r] 当中\)”,但是这里显然是一个值域的限制,而我们正好有一个契合莫队的值域分块。
它可以做到 \(O(1)\) 修改,\(O(\sqrt{n})\) 询问,于是这道题就直接利用值域分块来判断即可。
树上带修莫队+值域分块,询问树上路径第 \(k\) 大,单点修改,首先不考虑询问就是普通带修树上莫队,现在有了询问,发现值域分块也可以维护第 \(k\) 大,于是就做完了。
回滚莫队
回滚莫队用于维护一些只方便插入,不方便删除的信息(反过来也是),在题目只支持其中之一的时候可以考虑回滚莫队。
常见的题型有维护最值,\(mex\),众数等信息,常常和可撤销数据结构结合。
相对于普通莫队,其实这里就是每次左端点块都重置,而不是接着上一个块来,因为右端点本来就递增,所以可以保证只增加。
缺点是这个 \(O(n\sqrt{n})\) 跑的比较满,常数大。
基础回滚莫队
例题:

这里要维护最值,普通莫队不好删除(因为不好知道次大,次次大,次次次大...是多少),于是考虑回滚莫队,然后注意代码细节即可。
定义权值是 \(Cnt_i\times A_i\) ,求区间当中的最大权值。
发现这个最大值不好删除,于是考虑回滚莫队,然后直接和正常莫队一样做就好了。
给定一个序列,多次询问区间 \(mex\) 。
因为 \(mex\) 不好支持删除,于是可以直接考虑使用回滚莫队即可,但是我们发现其实插入也不好,于是考虑只维护删除操作,而不插入。
也就是只删不增的回滚莫队,这样我们的答案每次做多变化就是 1 了。
\(2021.5.13 \ update:\) 维护区间 \(mex\) 好像也可以使用 普通莫队+值域分块 ,也就是每次询问都直接立刻值域分块询问一次,修改很简单,这样好像要好一点。
这样做的话带修就要简单一点,直接带修莫队+值域分块即可,不然就得是带修回滚莫队了,见BSOJ1527&BZOJ4129 Haruna’s Breakfast,一道树上询问路径 \(mex\) 且带单点修改。

和模板题正好相反,同样可以回滚莫队,好像也可以普通莫队然后根号分治,具体不知道,但是这道题根号做法会被卡。

询问一个区间和为 0 显然可以想到前缀和,就直接可以转化成判断两点相等了。
然后就是模板题了,和模板题的唯一区别是这里要做一个前缀和,然后注意询问的左端点要 \(-1\) 。
带修回滚莫队
只是看到似乎可以,但是还没有求证实现过。
例题:
BSOJ1527&BZOJ4129 Haruna’s Breakfast
树上询问路径 \(mex\) 且带单点修改。
维护区间 \(mex\) 可以回滚莫队,然后这道题...可能可以用带修树上回滚莫队?
没实现过,用的 树上带修莫队+值域分块 。
回滚莫队+数据结构
因为回滚莫队的特性,所以通常,我们回滚莫队需要的数据结构需要支持撤销操作,比如可撤销并查集、可撤销分块(雾)等等。
例题:
树上选择两条简单路径,满足没有重合的点,且边权异或和之和最大。
可以转化成 \(dfn\) 序,就变成序列上的问题,但是区间不连续,于是复制一遍即可。
然后这里有个技巧就是如果把每个点的权值当做这个点到根的异或和,那么答案就相当于询问一个区间当中点对的异或最大值。
直接回滚莫队,异或最大值就用 \(01Trie\) 来维护。
给定一个序列,每次询问:给定 \(l,r,x,y\) ,问 \([l,r]\) 有多少个子区间满足其区间所有数使得 \(x\leq a_i \leq y\)。
序列上莫队显然不太行,于是直接对于值域进行莫队,然后题目发现可以转化成 \(01\) 序列统计极长子区间算贡献,因为这里值和位置一一对应,所以相当于每次移动指针,只会有一个位置进行 \(01\) 变换。
然后考虑维护,显然 \(0->1\) 好维护但是 \(1->0\) 不好维护,于是这里可以使用回滚莫队避免删除,然后区间的限制,我们就可以直接使用值域分块来维护全局,每次临时询问即可,注意分块要撤销。
二次离线莫队
某些时候,我们维护信息需要的复杂度过高,同时不好增加和删除,于是可以考虑这个原本问题去掉区间限制后的离线做法,那么这也就是莫队二次离线了。
二次离线莫队常用与转移复杂度过高的情况下。
基础二次离线莫队
注意四个方向的写法,至今都感觉很模糊。
例题:

二次离线莫队模板,这道题的重点就在于一个性质:最后满足结果的数只有 \(C^{7}_{14}\) 大约 \(3000\) 个,同时异或的性质可以直接移项,而这里单次转移复杂度太高。
于是可以考虑离线,转化成前缀和再差分的形式,就变成了一个可以预处理,一个可以离线来做的两个子问题,分开处理即可。
具体比较复杂,请看解答里面的描述。
P5047 [Ynoi2019 模拟赛] Yuno loves sqrt technology II
静态多次询问区间逆序对。
二次离线莫队模板。
莫队是显然的,然后考虑怎么维护端点的移动,直接维护该怎么做:求当前区间多少个数比 \(x\) 大,多少个数比 \(x\) 小。
然后直接二次离线来做,具体看解答部分,这里不再赘述。
二次离线莫队+数据结构
例题:

我们发现单点修改的贡献就是(假设是 \(r+1\),其中 \(num\) 是 \([l,r]\) 中比 \(x\) 小的 \(a[i]\) 的个数+1):\((num+1)\times x+\sum_{a[k]>x,k\in [l,r]}{a[k]}\)
这个可以直接树状数组维护,但是复杂度不能接受。
于是可以考虑二次离线莫队。
然后套路拆询问,拆成前缀相减的形式,然后可以分别维护比 \(x\) 大的数的 \(sum\) 和比 \(x\) 小的数的个数乘以 \(x\) 的值。
然后一个预处理,一个离线后使用值域分块维护即可。
在线莫队
还没学,待补。
二维莫队
还没学,待补。

浙公网安备 33010602011771号