莫队小记
复杂度懒得又 \(n\) 又 \(m\) 的了,所以统一写 \(n\) 了。其实是不会(
就随便记点细节方面的内容,希望能帮助到一些人。
主要参考 oi-wiki,加入了很多随机的细节记录。
普通莫队
维护区间 \([l,r]\to[ql,qr]\),每次暴力移动指针,要求能够快速处理指针 \(\pm1\) 对答案带来的变化。
奇偶排序,取 \(B=\sqrt n\) 移动次数上界 \(O(n\sqrt n)\)。
不使用奇偶排序而是分块排序(不同块按 \(l\) 排,同块内按 \(r\) 排)复杂度也是对的
具体证明没啥用不看了
移动指针先扩大区间(l--
,r++
)再缩小区间(l++
,r--
)!!!!!
\(\text{ }\)
高维莫队
设有 \(k\) 维,取 \(B=n^{\frac{k-1}k}\) 有复杂度 \(O(n^{\frac{2k-1}k})\)。
复杂度证明没啥用不看了
唐氏四维莫队题:https://www.luogu.com.cn/problem/CF1767F
但是这个题四维莫队做法太唐了点。。。合理猜测其他用高维莫队的题也会比较唐(?)
\(\text{ }\)
带修莫队
将时间轴扔进莫队中,其实就是三维莫队,取 \(B=n^{2/3}\) 有复杂度 \(O(n^{5/3})\)。
维护时间轴移动时需要特殊处理一下,因题目而异。
\(\text{ }\)
回滚莫队
处理 add()
容易 del()
难的问题。
以这个题为例,https://www.luogu.com.cn/problem/AT_joisc2014_c
首先要使用朴素分块排序,
这样同一个块内的询问右端点递增。
每次到下一个块时钦定 \(l\gets R_i+1,r\gets R_i\) 表示空区间,然后暴力清空维护的信息。显然 \(O(n\sqrt n)\)
若询问的左右端点在同一个块内,直接特判掉,暴力显然单次 \(O(\sqrt n)\)
否则 \(qr>R_i\),由于这样同一个块内的询问右端点递增,这里 \(r\) 可以不回退地移动,于是均摊 \(O(n)\);然后 \(l\) 就暴力地移到 \(ql\) 的位置就行,移动完再回溯回去。\(l\) 在同一个块内,所以暴力移动是 \(O(\sqrt n)\) 的。
\(l\) 移动这里是有正确性的,因为 \(l\) 总是处于块的最右端,所以 \(l\to ql\) 必定是扩大区间(往左边移),而尽管回溯需要进行 del()
操作,但是显然回溯是不会对答案造成影响的,所以随便搞搞就行啦,具体可以看代码。
所以复杂度是 \(O(n\sqrt n)\),很厉害
洛谷模板题 https://www.luogu.com.cn/problem/P5906
写代码可以发现会麻烦一些,因为要维护 \(st_i,ed_i\) 表示某个数的最左 / 右的位置。
这里把代码放出来
inline void addl(int x, int &qwq) {
if (!ed[a[x]]) ed[a[x]] = x;
//st[a[x]] = x;
qwq = max(qwq, ed[a[x]] - x);
}
inline void addr(int x, int &qwq) {
if (!st[a[x]]) st[a[x]] = x;
ed[a[x]] = x;
qwq = max(qwq, x - ed[a[x]]);
}
inline void del(int x) {
if (st[a[x]] == x) st[a[x]] = 0;
if (ed[a[x]] == x) ed[a[x]] = 0;
}
发现 addl()
里面不能有 st[a[x]] = x
这一句话,有了还会错??
实际上,观察上述流程,发现 addl()
只会出现在那个临时移动中,如果更新了 st[]
的话会在回溯时的 del()
被删空,而发现直接不更新的话,其实是对答案没影响的。。然后就没问题了。
\(\text{ }\)
莫队二次离线
处理 add()
,del()
复杂度较高的题。
其实就是注意到,设 \(f(x,l,r)\) 表示 \(x\) 对区间 \([l,r]\) 的贡献,每次本质是不断调用这个 \(f\) 维护 add()
,del()
函数。而这个 \(f\) 完全是可以离线下来搞的,也就是先莫队一次,记下需要调用的所有 \(f\),然后再运用一些离线技巧快速处理 \(f\) 的值。这样相当于是可以把 add()
,del()
带来的搞复杂度从根号拉出去。
\(O(n\sqrt n+\text{离线复杂度})\),空间维护连续段的话可以做到 \(O(n)\),很厉害。
好像很多人对洛谷模板题一个判 k=0
的地方有问题,其实你可以试试写一个暴力莫队,然后发现你那样差分就是会在 \(l\) 移动时少算贡献。总之写出式子就是非常显然的,不知道是不是大家都抄题解所以没有注意到这个显然的事实(
\(\text{ }\)
树上莫队
用欧拉序拍成序列即可。
对于查询 \(u,v\) 间信息的题,只需要预处理 \(st_i,ed_i\) 表示 \(i\) 进入 / 离开树在欧拉序上的位置,然后对 \(u,v\) 的祖先关系分类即可。有祖先关系就取 \([st_u,st_v]\),否则取 \([ed_u,st_v]\) 然后额外添加一个 LCA 即可,总之画画图就好理解了。
OI-Wiki 上的真·树上莫队就是树分块捏,我感觉那个的关键点是树分块而不是莫队,不写了。
\(\text{ }\)
值域分块
懒得再开一篇 blog 了,就随便记在这里了((
值域分块在平衡复杂度中非常有用,与莫队 / 莫二离可以比较优秀地结合。
最简单的例子:修改是单点修,查询是查一段值域区间的数的和
- \(O(1)\) 修 \(O(\sqrt n)\) 查:每个块维护值域内数的和,修改直接修改,查询正常分块查询即可。
- \(O(\sqrt n)\) 修 \(O(1)\) 查:改成维护块与块间 & 块内的前缀和即可。查询直接差分。