莫队学习笔记
基础的莫队比较简单就略过了。
好吧还是说一下。
普通莫队
莫队其实是个暴力算法。
- 情景
假设你现在需要处理一个问题,它给出了一个长度为 \(n\) 的序列,并给出了 \(m\) 组区间询问。
这样的一个问题是不难的,但是你发现了一个令人非常窒息的事情:询问的贡献非常难算。
好在,绝望中仍有一线生机,这个题目的贡献有一个非常有趣的性质:
如果我们令询问区间为 \([l,r]\) 时的答案为 \(f(l,r)\),那么你可以通过一些计算得到 \(f(l\pm 1,r)\) 和 \(f(l,r\pm 1)\)。
(下面我们把这称之为一次移动,两种方式分别对应左移和右移。)
- 思路
现在,莫队出场了。
如果我们暴力去做这个问题,有个非常显然的思路是直接进行 \(\mathcal{O}(n)\) 次计算,从上一次询问的 \(f(l,r)\) 一路推到现在询问的 \(f(l',r')\),这样总的移动次数是 \(\mathcal{O}(nm)\) 的。
注意到这里的移动有可能被极限数据卡到每次都要从头到尾跑一遍,也可能被极限数据直接压缩成 \(\mathcal{O}(n)\) 次。
这启发我们,是否存在这样一种方式,我们可以通过合适地排列询问来降低时间复杂度?
事实上,的确是可以的。
我们先将所有询问按照 \(l\) 从小到大排序,这样我们处理所有询问的左移总次数就是 \(\mathcal{O}(n)\) 的。
此时我们的 \(r\) 是杂乱无序的,我们同样想使其有序,但这样会反过来让 \(l\) 完全无序。
既然如此,我们折中一下,把所有询问分组,一组内的询问按照 \(r\) 排序,这时候组内的 \(l\) 是杂乱无序的,但由于此前已经进行过排序,\(l\) 在不同的组直接存在非严格的偏序关系。
准确来说,此时第 \(i\) 组内的所有询问的 \(l\) 都是小于等于 \(i+1\) 组内的询问的 \(l\) 的。
这样的话,我们让一组内的 \(l\) 的极差恰为 \(\sqrt{n}\),一共就只有 \(\dfrac{n}{\sqrt{n}}=\sqrt{n}\) 个组。
对于组内的询问,右移的总次数是 \(\mathcal{O}(n)\) 的,把每个组的算到一起,右移的总次数也就是 \(\mathcal{O}(n\sqrt{n})\) 的。
而对于左移而言就更简单了,由于两个相邻的询问要么在同一组,要么不在同一组,两种情况左移的次数均为 \(\mathcal{O}(\sqrt{n})\) 级别的,所以总的左移次数也就是 \(\mathcal{O}(m\sqrt{n})\) 的。
于是莫队的总移动次数就是 \(\mathcal{O}((n+m)\sqrt{n})\)。
注意到这里由于单次移动的复杂度不一定是 \(\mathcal{O}(1)\),所以没有直接说明莫队的时间复杂度。
- 奇偶性优化
这个优化非常简单:两个相邻的组的 \(r\) 的排序方式相反即可,即前一个组从小到大排序,后一个组就从大到小排序。
优化的原理也非常简单,前一个组右移结束后目前已知的 \(f(l,r)\) 的 \(r\) 肯定是前一个组中最大的,我从最大的重新开始处理,\(r\) 就不用再跳回到最小值去了,这样可以节约一个 \(2\) 的常数,非常给力。
至于莫队的其它小优化,都没有这个给力就不提了。
- 例题
由于普通莫队不是重点,例题也就不多说了。
高维莫队
拿来打暴力的,实际应用非常之少,没什么可说的。
有兴趣可以看这篇文章。
回滚莫队
非常有趣的一个莫队变体。
- 情景
还是考虑前面给的模型,这次我们的神秘条件变了:
如果我们令询问区间为 \([l,r]\) 时的答案为 \(f(l,r)\),那么你可以通过一些计算得到 \(f(l-1,r)\) 和 \(f(l,r+1)\)。
哎,这下只能往已知的区间内添加元素而不能删去了,这怎么办呢?
- 思路
不慌,回滚莫队正是来解决这个问题的。
其思路也非常简单粗暴:你不是不能删除么?那我只需要把所有可能的结果全部存下来不就好了。
具体来说,设当前组的 \(l\) 的取值范围为 \([x,y]\),我们直接暴力处理所有 \([p,y],p\in[x,y]\) 的答案,然后再和 \([y+1,r]\) 的答案拼起来就好了。
这里要注意一个细节:如果 \(r\le y\),我们应该直接暴力求解这组询问。
然后就没了……回滚莫队就这么简单……吗?
注意到一个非常有趣的事情,上面这个需要区间可加性,但是显然神秘条件不能推出区间可加性,没有区间可加性就直接废了。
(话说有区间可加性不应该直接用线段树了吗……)
这时候我们可以换个方式实现。
对于 \(r\le y\) 的询问,我们还是考虑暴力解决,剩下只考虑 \(r\gt y\) 的情况。
我们可以从 \(f(y,r)\) 开始一步一步推到 \(f(l,r)\),移动次数显然是 \(\mathcal{O}(\sqrt{n})\) 级别的。
然后我们再通过撤销操作,从 \(f(l,r)\) 倒带到 \(f(y,r)\),然后再继续处理后续的询问。
这非常好对吧?但是如果操作不可撤销或撤销复杂度过大也就废了。
所以回滚莫队并不一定能够代替普通莫队,两者都各有所长却各有所缺。
- 优化
显然你还是可以进行奇偶性优化。
由于回滚莫队的常数很大,强烈建议使用奇偶性优化。
- 例题
由于我比较懒就贴一个之前的题解啦。
二次离线莫队
LXL 开发的神秘算法。
- 情景
考虑一个普通莫队,单次移动的复杂度不是 \(\mathcal{O}(1)\) 了,有没有办法使其复杂度保持为根号?
- 思路
有些时候,我们确实能够实现这件事。
考虑莫队每次移动的时候,实际上也是在询问一个东西,这个询问有时候可以拿各种数据结构维护不是么?
那么众所周知,很多时候离线可以诞生许多神奇算法,这就是二次离线的含义。
二次离线莫队的本质就是把莫队的移动的询问存下来,用一个离线算法求解出所有的询问。
- 例题
结合例题讲解最实在了。
LG5047 Ynoi2019 Yuno loves sqrt technology II
题意:给出长度为 \(n\) 的序列 \(a\) 和 \(m\) 组询问,每次询问一个区间内的逆序对数量。
考虑莫队,从 \(f(l,r)\) 进行移动的过程是怎样的呢?
考虑 \(f(l,r+1)\),显然 \(f(l,r)\) 对应的逆序对仍然存在,我们只需要计算 \(a_{r+1}\) 带来的新的贡献即可,也即查询 \(a_{r+1}\) 和 \([l,r]\) 中的多少数能组成逆序对。
由于 \(a_{r+1}\) 在 \([l,r]\) 的右边,这个询问就是在查询 \([l,r]\) 中比 \(a_{r+1}\) 大的数的数量。
这个只需要维护一个桶,然后用树状树组维护这个桶就可以了,应该比较简单就不赘述了。
对于其它的三种移动,都是同理的,也不再赘述,这样可以得到一个 \(\mathcal{O}((n+m)\sqrt{n}\log n)\) 的做法。
显然我们好心(毒瘤)的 LXL 是不会让这个复杂度过的,这已经不是常数问题了。
观察莫队移动时的询问,这个询问非常有趣。
我们设 \(A(l,r,x)\) 表示在区间 \([l,r]\) 中有多少大于 \(x\) 的数。
容易发现,\(A(l,r,x)=A(1,r,x)-A(1,l-1,x)\),那么此时我们只需要把 \(x\) 的询问挂到 \(r\) 和 \(l-1\) 上,做一遍扫描线即可求出答案。
这里的意思是,我们扫描线扫描到 \(p\) 的时候,就把 \([1,p]\) 这个前缀的信息全部得到了,使用树状树组等数据结构,显然容易得到 \(A(1,p,x)\) 的信息(根据 \(A\) 的定义,这是好求的),这里 \(x\) 是任意值。
现在的问题是,我们应该要选用一个合适的数据结构维护答案,仔细分析,由于只做一次扫描线,进行修改的次数是 \(\mathcal{O}(n)\) 级别的,而询问数量和莫队移动数同级,为 \(\mathcal{O}(n\sqrt{n})\)(认为 \(n,m\) 同阶)。
不难发现询问数和修改数完全不对等,可以考虑使用 \(\mathcal{O}(\sqrt{n})\) 修改 \(\mathcal{O}(1)\) 查询的分块来平衡复杂度,这里这个分块是非常容易的。
简单说一下这个分块:
基本思路是每个值存一个桶,记录这个值有多少个。
块内进行一个前缀和可以查询块内的结果,块外同样进行一个前缀和可以查询块外的结果。
此时我们解决了右移的问题,至于左移是类似的,我们记录一个 \(B(l,r,x)\) 表示在区间 \([l,r]\) 中有多少小于 \(x\) 的数,处理方法是一样的。
于是我们在 \(\mathcal{O}(n\sqrt{n})\) 的时间复杂度内求出了所有莫队移动所需的贡献,总的时间复杂度就是 \(\mathcal{O}(n\sqrt{n})\)(认为 \(n,m\) 同阶)。
- 优化
注意到上面这题的空间限制非常夸张,如果我们暴力存储所有询问会直接挂掉。
莫队在移动的时候一定是一连串的移动,我们把一连串的移动拿一个东西记录下来,然后正式进行询问的时候再将其展开即可。
这时候有个问题,对于 \(A(1,l-1,x)\) 是好存的,我们只需要将移动的询问整个挂在 \(l-1\) 的位置即可。
但是对于 \(A(1,r,a_{r+1})\),\(r\) 会随着移动发生变化,没办法把所有询问挂到一起。
这时候的处理方法比较多,一种很常见的处理方法是预处理 \(sum_i=\sum\limits_{j=1}^{i-1}A(1,j,a_{j+1})\),这样直接差分即可获得一整个区间的 \(\sum A(1,r,a_{r+1})\)。
这样可以直接把空间优化到线性,大部分二次离线莫队的题都卡了空间。
另外二次离线莫队同样可以使用奇偶性优化,由于其优化的是移动次数,仍然能砍掉接近 \(2\) 的常数,所以写莫队要养成习惯写奇偶性优化。
- 注意
二次离线莫队显然是扩展性非常之高的算法,而且根据第二次离线的数据结构不同,难度也有较大差异,是非常灵活的一类题目,可以综合考察选手的数据结构能力。
这也使得它与前面几个莫队有着很大的不同,需要多加练习才能掌握。
大部分二次离线莫队的题都可以先考虑普通莫队,但是注意,离线算法能够解决一些无法在线解决的问题,这意味着有时候二次离线莫队能解决一些普通莫队难以解决的问题。
- 例题
我太太太太懒了,题解就去题解区看吧。
和上面那个几乎一样的板子题。
不建议考虑普通莫队。
推式子题,二离只是辅助。
真正的难点在于第二次离线的数据结构(和卡常)。

浙公网安备 33010602011771号