莫队

普通莫队

莫队通过将所有询问按某种顺序排序,并暴力地增、删元素来在询问间转移。

莫队的适用条件是,信息不能高效地合并,但可以高效地加入、删除。具体地,若加入、删除一个元素的复杂度都为 O(a),那么莫队的复杂度是 O(nam)

复杂度分析

我们将序列分成大小约为 S 的若干段,段个数约为 nS。对于所有询问,以左端点所在块为第一关键字,右端点为第二关键字排序。考虑在同一个块内,指针移动的复杂度:左端点会贡献 O(xS),其中 x 是左端点在这个块内的询问个数;右端点会贡献 O(n)。所以总复杂度是 O(mS+n2S),当 S=nm 时最优,为 O(nm) 次指针移动。

常数优化

对于左端点在奇数块的询问,将其按照右端点从小到大排序;左端点在偶数块的询问,右端点从大到小排序。

[AHOI2013] 作业

给定整数序列 {an}m 次查询区间 [l,r] 内值在 [a,b] 中的不同数的个数。

1n105,1m105

如果对序列维做莫队,用树状数组维护每种数是否出现,这是 O(nmlogn) 的。但我们发现它有 O(nm) 次修改和 O(m) 次询问,若用分块将修改和询问的复杂度分别弄成 O(1)O(n),就做完了。

值域分块,它需要支持 O(1) 单点修改,O(n) 区间求和,直接做就行了。

总复杂度 O(nm+mn)

[Ynoi2016] 这是我自己的发明

给定一棵有根树,每个点有颜色 ai,开始时根是 1。有 m 次操作,每次是以下两种之一:

  • 给定 x,将根更改为 x
  • 给定 x,y,询问有多少个二元组 (u,v) 满足 ux 的子树内,vy 的子树内,且 au=av

我们以 1 为根求出每个点的 dfs 序。不管根是哪个点,一个点的子树对应在 dfn 上一定是 O(1) 个区间。有三种可能:

  • 根在 u 的某个儿子的子树内;
  • 根是点 u
  • 根不在点 u 的子树内。

所以每个询问都可以拆成 O(1) 个形如“给定 l1,r1,l2,r2,求 [l1,r1][l2,r2] 中有多少颜色相同的点对”的询问。而这样的询问是可差分的。莫队即可。

坑点:应该将点的颜色映射在 dfn 上。

代码:https://github.com/YOYO-UIAT/oi-code/blob/main/luogu/P4689.cpp

JOISC 2014 历史研究

给定整数序列 {an},定义 cl,r(x)xalr 中的出现次数,m 次询问,每次给定区间 [l,r],求 maxx{xcl,r(x)}

1n,m105

不用回滚莫队。可以发现答案只有 O(n) 种,将这些可能的答案放在一起离散化,然后值域分块即可。复杂度 O(nm+mn)

代码:https://github.com/YOYO-UIAT/oi-code/blob/main/luogu/AT1219.cpp

[Ynoi2015] 盼君勿忘

给定整数序列 {an}m 次询问,每次给定区间 [l,r] 和模数 p,求区间中所有子序列去重后的和 modp

1n,m,ai105

设询问的区间长度为 L,对于出现了 c 次的数 x,它对答案的贡献是 x(2L2Lc)x2L 的维护是很平凡的,只需维护区间中出现过的数之和。考虑 x2Lc 如何维护:可以发现区间中不同的出现次数只有 O(n) 种(这是常见套路),所以维护每一种出现次数的 x 之和,回答询问时光速幂即可。

P3604 美好的每一天

给定一个由小写字母组成的字符串 Sm 次询问某个区间内有多少子区间可以重排成一个回文串。

1|S|,m6×104,3s。

首先,一个字符串 T 可以重排成回文串,当且仅当其中有至多一种字母出现了奇数次。我们将 az 状压成 20,21,,225,那么就是询问一个区间内有多少子区间满足它的异或和的 popcount1。又因为区间异或和可以用前缀异或和表示,所以就做完了。

高维莫队

我不会证的结论:对于 k 维莫队,假如有 m 个询问,且每一维的大小限制分别为 a1,a2,,ak,那么将前 k1 维的分块大小都设成

i=1kaimk

是最优的,时间复杂度是

O(mk1i=1kaik).

二次离线莫队

对于一些问题,普通莫队的做法是维护一个变量表示当前的答案,然后用数据结构维护一些信息来更新这个答案,这会在数据结构上产生 O(nm) 次询问和 O(nm) 次修改。若询问可差分,我们可以将在数据结构上的询问再次离线,然后做扫描线。

P5047 [Ynoi2019 模拟赛] Yuno loves sqrt technology II

给定整数序列 {an}m 次查询一个区间的逆序对个数。

1n,m105,250ms,32MB。

F(l,r,p)=lir[ai<ap],G(l,r,p)=lir[ai>ap],设当前答案为 ans。在莫队过程中:

  • l 左移:ll1,ansans+F(l+1,r,l)
  • r 右移:rr+1,ansans+G(l,r1,r)
  • l 右移或 r 左移:同理。

显然 FG 都是可差分的,所以关于 ans 的更改可以分为:(1) F(1,x,x)G(1,x,x);(2) F(1,x,y)G(1,x,y)

对于第一种,树状数组与处理即可;对于第二种,将所有这样的询问做扫描线,用 O(n) 单点修改,O(1) 区间求和的分块即可。

这样时间复杂度可以做到 O(n(n+m)),但空间也带根号,会被卡空间。但考虑到每次移动指针都是一段连续的询问,所以只需要存 O(m) 个询问即可。具体细节参见代码。

代码:https://github.com/YOYO-UIAT/oi-code/blob/main/luogu/P5047.cpp

树上括号序莫队

用于处理查询树上一段链的信息,可以做到与普通莫队相同的时间复杂度。

将树映射到括号序上,每个点在括号序中恰好出现两次,位置设为 luru。设当前查询的是 xy 的链上的信息:

  • lca(x,y)=x,则用莫队计算括号序中在 [lx,ly] 中恰好出现过一次的点的信息;
  • 否则,计算 [rx,ly] 中恰好出现过一次的点的信息,并将 lca 的信息额外加上。

带修莫队

加一维时间维,和三维莫队本质相同。假如有 c 个修改和 q 个询问,那么分块的大小是 n2cq3,时间复杂度是 O(n2cq23)。当然块长一般都会直接设成 n2/3

WC2013 糖果公园

给定一棵 n 个点的树,m 次操作,每次是单点修改或者查询一段链上的简单莫队信息。

1n,m105,6s。

直接用树上带修莫队做就行了。注意若修改的次数是 0,块长要特判。

代码:https://github.com/YOYO-UIAT/oi-code/blob/main/luogu/P4074.cpp

回滚莫队

假如只能支持快速加入或者删除,并且支持快速撤销,仍然可以做到 O(nm) 的时间复杂度。

P5906 不删除莫队

给定整数数列 {an},多次询问某个区间 [l,r] 内的 maxli<jrai=ajji,若不存在则输出 0

1n,m2×105

将序列分成 nm 块,按照左端点所在块的编号为第一关键字、右端点为第二关键字,将所有询问排序。对于每一个询问:

  1. 若当前左端点与上次的左端点不在同一个块内,撤销所有操作,并将莫队的指针放在当前块的右端点;
  2. 若左端点与右端点在同一个块内,暴力求解,转 1
  3. 移动莫队的右指针;
  4. 移动莫队的左指针,然后计算答案;
  5. 撤销 4 中的操作。

可以理解为,对于每一个块,左端点在这个块内的询问,右端点是单调递增的,所以在右端点处只会加不会删;对于左端点,它移动的距离每次不超过块大小,所以每次计算完暴力撤销复杂度就是对的。

代码:https://github.com/YOYO-UIAT/oi-code/blob/main/luogu/P5906.cpp

不添加莫队:P4137 Rmq Problem / mex

多次询问某个区间的 mex

1n,m2×105

与不删除莫队相似,我们先将询问排序,但这次对于右端点要降序排序。对于每一个询问:

  1. 若当前左端点与上次的左端点不在同一个块内,撤销直到莫队的右指针到达 n,然后删除直到莫队的左指针到达当前块的左端点;
  2. 不需要暴力求解
  3. 移动莫队的右指针;
  4. 移动莫队的左指针,然后计算答案;
  5. 撤销 4 中的操作。

代码:https://github.com/YOYO-UIAT/oi-code/blob/main/luogu/P4137.cpp

PS:这题有非常简单的 1log 做法。

作者:alan-zhao-2007

出处:https://www.cnblogs.com/alan-zhao-2007/p/mo-algorithm.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Alan_Zhao_2007  阅读(314)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示
more_horiz
keyboard_arrow_up dark_mode palette
选择主题