莫队小记

复杂度懒得又 \(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)\) 查:改成维护块与块间 & 块内的前缀和即可。查询直接差分。
posted @ 2025-04-29 20:44  liangbowen  阅读(18)  评论(0)    收藏  举报