莫队 - 基础与扩展

article/2025/8/8 21:45:46

普通莫队

莫队可以说是一个算法,但更多是一种思想。

我们先来看看普通莫队解决的问题:

有一个长度为 n n n 的数列 a a a
q q q 个询问: a a a [ l i , r i ] [{l_i},r_i] [li,ri] 中有多少个不同的数。
不强制在线。 1 ≤ n , q ≤ 5 × 1 0 5 1\leq n,q\leq5\times10^5 1n,q5×105

遇到这种区间问题,第一个想法是前缀和,但很快会发现不可行。于是你搬出了树状数组,切了这道题(

但总有一些人觉得树状数组太难写了。

于是考虑其他做法。

如果目前知道了 [ l , r ] [l,r] [l,r] 这个区间内的答案,你可以在 O ( 1 ) O(1) O(1) 时间内求出 [ l − 1 , r ] , [ l + 1 , r ] , [ l , r − 1 ] , [ l , r + 1 ] [l-1,r],[l+1,r],[l,r-1],[l,r+1] [l1,r],[l+1,r],[l,r1],[l,r+1] 这些区间的答案。

具体操作是:

用一个数组 b i b_i bi 记录当前区间 [ l , r ] [l,r] [l,r] 中每个数字出现个数。
端点左移或右移后,区间会添加或减少一个数 x x x,让 b x b_x bx 对应更改,判断 b x b_x bx 是否从 0 0 0 变成非 0 0 0 或者从非 0 0 0 变成 0 0 0
如果有,就将这个区间内的答案对应加 1 1 1 或减 1 1 1

这样,通过若干次移动,我们可以从上一个询问 [ l i − 1 , r i − 1 ] [l_{i-1},r_{i-1}] [li1,ri1] 的答案,推出下一个询问 [ l i , r i ] [l_i,r_i] [li,ri] 的答案。但每次需要一定时间。

于是尝试能否通过前面询问答案快速推出后面询问答案。

将所有询问按照二元组 ( l , r ) (l,r) (l,r) 排序,可能可以实现上面的要求。

但是时间复杂度是 O ( n 2 ) O(n^2) O(n2) 的。

那么需要换一种排序方法:

先将询问按照左端点 l l l 排序。
将数组分成 n \sqrt n n 块,
最后对于 l l l 在同一块内的询问,对于 r r r 再重新排序。

其实这种排序方法简化就是:

n n n 个询问分块。
对询问按以 l l l 所属块编号升序为第一关键字, r r r 升序为第二关键字的方式排序。

这时候可以证明当 n , q n,q n,q 同阶时,时间复杂度是 O ( n n ) O(n\sqrt n) O(nn ) 的。

块内,左端点每询问移动不超过 n \sqrt n n 次。共有 q q q 个询问。所以时间复杂度为 O ( n q ) O(n\sqrt q) O(nq )
每块,计算一开始的答案时间复杂度 O ( n ) O(n) O(n)。共有 n \sqrt n n 个块。所以时间复杂度为 O ( n n ) O(n\sqrt n) O(nn )
每块,右端点移动不超过 n n n 次。共有 n \sqrt n n 个块。所以时间复杂度为 O ( n n ) O(n\sqrt n) O(nn )

以上是普通莫队,莫队的最基础用法。

当然莫队还有各种变化和优化。

反复横跳奇偶优化

当我们在块间移动左右端点时,会发现需要移动很多次,导致时间暴增。

其中一部分原因是由于每块中右端点 r r r 都是按照增序排序的。会导致每次需要从最右端移到最左端。

所以,理所当然的,对于第奇数个块,让右端点 r r r 按照增序排序,对于第偶数个块,让右端点 r r r 按照降序排序。

别看优化简单,但是作用非常大。

带修莫队

顾名思义,带修改的莫队。

例题:

有一个长度为 n n n 的数组 a a a
q q q 个操作:

  • 询问 a a a [ l , r ] [l,r] [l,r] 中有多少个不同的数。
  • a i a_i ai 替换成 v v v

不强制在线。 1 ≤ n , q ≤ 5 × 1 0 5 1\leq n,q\leq5\times10^5 1n,q5×105

这时只需加入一个时间戳,将询问变成 ( l , r , t ) (l, r, t) (l,r,t) 即可

移动操作自然变成了六种。

排序方法则是

把数组分成 n 3 t 4 \sqrt[4]{n^{3}t} 4n3t
以左端点所在块为第一关键字,右端点所在块为第二关键字,时间戳为第三关键字,升序排序。

为什么不是 n \sqrt n n 呢?有一位大佬证明过这种情况下 n 3 t 4 \sqrt[4]{n^{3}t} 4n3t 是最优的。

然后每次就按顺序移动左右端点和时间戳计算答案即可。

树上莫队

数组上的莫队我知道,但树上莫队是干啥的?

路径问题

有一棵 n n n 个节点树,每个节点有一个值 a a a
q q q 个询问,每次询问节点 u u u 到节点 v v v 之间路径上有多少不同值。
不强制在线。 1 ≤ n , q ≤ 1 0 5 1\leq n,q\leq10^5 1n,q105

可以想到,如果我们把每个询问表示成 ( u , v ) (u,v) (u,v),那么是可以从一个询问的答案 O ( 1 ) O(1) O(1) 推到另一个询问的答案的。

但是,问题来了,怎么排序?我们并不知道如何排序才能保证时间复杂度。

所以最终还是需要回归数组。

尝试将这棵树的信息用数组储存,并且这个数组满足一个区间的答案可以 O ( 1 ) O(1) O(1) 推到另一个区间的答案。

这时就需要用到欧拉序了。(欧拉序就是在 d f s dfs dfs 的时候进入和离开一个节点都将节点记录后形成的序列)

首先将这棵树的欧拉序用 d f s dfs dfs 跑出来。
在这里插入图片描述
如图,这棵树的欧拉序为 A B F C C F D D H H B E G G E A ABFCCFDDHHBEGGEA ABFCCFDDHHBEGGEA,我们记录下每个节点前后两次出现的位置 L i L_i Li R i R_i Ri

现在要做的就是把每个询问的 [ u , v ] [u, v] [u,v] 转到欧拉序上的一个区间 [ l , r ] [l,r] [l,r]

可以发现,如果把欧拉序上一个区间 [ l , r ] [l, r] [l,r] 内所有出现两次的点删除,则剩余的点可以组成一个路径。

尝试从路径推出区间,分成路径两种情况:

  1. 路径首尾节点为祖先关系。则相当于区间 [ L u , L v ] [L_u,L_v] [Lu,Lv] [ R v , R u ] [R_v, R_u] [Rv,Ru]
  2. 路径首尾节点不为祖先关系。则相当于区间 [ R u , L v ] + l c a ( u , v ) [R_u, L_v]+lca(u,v) [Ru,Lv]+lca(u,v) [ R v , L u ] + l c a ( u , v ) [R_v, L_u]+lca(u,v) [Rv,Lu]+lca(u,v)

将所有路径转换为区间,就可以使用普通莫队解决问题了。

子树问题

有一棵 n n n 个节点树,每个节点有一个值 a a a
q q q 个询问,每次询问节点 u u u 的子树上有多少不同值。
不强制在线。 1 ≤ n , q ≤ 1 0 5 1\leq n,q\leq10^5 1n,q105

同样,还是考虑如何把树上问题转换成区间问题。

显然,我们只需要用 d f s dfs dfs 序标记节点,即可使每个子树节点编号连续。

而且这还不需要分类讨论。

回滚莫队

有一个长度为 n n n 的数组 a a a。设 c n t i , l , r cnt_{i,l,r} cnti,l,r 为在 [ l , r ] [l,r] [l,r] 区间内 i i i 的出现次数。
q q q 次询问,询问 [ l i , r i ] [l_i, r_i] [li,ri] 区间内最大的 c n t j , l i , r i × j cnt_{j, l_i, r_i}\times j cntj,li,ri×j
不强制在线。 1 ≤ n , q ≤ 1 0 5 1\leq n,q\leq10^5 1n,q105

我们先按照普通莫队的思路来思考。

首先,考虑区间端点能否快速移动。

[ l , r ] [l,r] [l,r] 区间右端点 r r r 向右移动或左端点 l l l 向左移动,我们只需把新增的数的出现次数乘上自己后与最大值取 m a x max max 即可。

但如果是左端点 l l l 右移,右端点 r r r 左移呢?我们发现难以 O ( 1 ) O(1) O(1) 解决。

怎么办?

我们干脆不左端点不左移,右端点也不右移了。

排序还是按照原来的方法:

n n n 个询问分块。
对询问按以 l l l 所属块编号升序为第一关键字, r r r 升序为第二关键字的方式排序。

然后过程有所改变:

设当前询问所在块左端点为 s i s_i si,右端点为 t i t_i ti
每块初始化:

  1. 我们将莫队区间 [ l , r ] [l, r] [l,r] 的左端点 l l l 重置到 t i + 1 t_i + 1 ti+1,右端点重置到 t i t_i ti,此时莫队区间为空。

回答块内询问:

  1. 如果莫队区间右端点在询问右端点右边,则代表询问左端点和右端点处于同一块内,直接暴力计算。
  2. 将莫队区间右端点 r r r 移动到询问右端点。
  3. 记录下当前莫队区间答案。
  4. 将莫队区间左端点 l l l 移动到询问左端点。
  5. 回答询问。
  6. 使用先前记录的答案重置莫队区间,使左端点 l l l 回到 t i + 1 t_i + 1 ti+1

http://chatgpt.dhexx.cn/article/IgJUSE5K.shtml

相关文章

莫队详解

莫队实际很简(du)单(liu) 依照某位dalao的说法,就是两只小手(two-pointers)瞎跳 一.莫队(静态莫队) 我们以Luogu P3901 数列找不同为例讲一下静态莫队 这道题是个绿题&am…

关于莫队

待补:树上莫队,二次离线莫队 关于莫队 其实是个神奇的暴力算法.. 通常就是一种处理区间的东西 通过上一次的的左端点和右端点跳来跳去 从而得到这次区间的值 很暴力啊...完全不用思考就能懂的 那么 接下来首先是 普通莫队 排序和移动两点提及一下 …

莫队(普通莫队,带修莫队,树上莫队)

听说 莫队算法是一种“优雅的暴力”(小声bb)。 普通莫队 1/ 引入 problem:给你一个长度为n的数组,有m次查询,每次查询询问一个区间[L, R]内有多少个不同的数。 首先想想暴力怎么做。 开一个数组用来记数&#xff0c…

回滚莫队解析

(南昌理工ACM集训队) 泻药,不匿(bushi) 浅谈莫队回滚莫队样例小结 浅谈莫队 莫队是要摸清回滚莫队必须掌握的前置技能(๑ŐдŐ)b 前两天打多校有幸接触了莫队算法,花了几天时间搞懂了这些东西&#xff0…

【莫队/树上莫队/回滚莫队】原理详解及例题:小B的询问(普通莫队),Count on a tree II(树上莫队),kangaroos(回滚莫队)

文章目录 问题引入介绍莫队算法及其实现过程时间复杂度莫队算法适用范围莫队奇偶优化普通莫队:小B的询问树上莫队:SP10707 COT2 - Count on a tree II回滚莫队:[PA2011]Kangaroos upd:2021-08-11:重新对博客进行了外观…

超全的莫队算法一遍过

前置知识:双指针、分块 简单概括而言,莫队是一个离线算法,核心思想是将所有的询问按照分块进行排序后,对于每个询问可以通过双指针增删数据来达到总体的复杂度。 莫队通常用来解决求一段区间内不同数的个数相关的问题。 目录 1、…

莫队(普通操作)C++

由于&R闲着无聊,今天上午学习的莫队也并不是完全掌握,老师才发的PPT也没有保存,于是决定乱写一篇靠着自己的理解写一篇关于莫队用法的博客......如果有错的麻烦之处,纯属&R自己理解有问题哈。 一.莫队的含义 莫队是在一…

【算法笔记】莫队算法(基础莫队,带修莫队,回滚莫队,树上莫队,二次离线莫队)

整理的算法模板合集: ACM模板 目录 1. 基础莫队2. 带修莫队3. 回滚莫队4. 树上莫队5. 二次离线莫队6. 在线莫队 来这里学习莫队以及神奇的证明:莫队算法 --算法竞赛专题解析(26) 我们首先考虑双指针的暴力法,发现很容…

linux(vscode)配置coderunner for python3

1.Python3 安装路径查看 which python3 2.vscode扩展中安装coderunner 3.配置coderunner 管理->设置->扩展->coderunner,找到下图所示内容,选择在settings.json中编辑。 在文件中输入code-runner.executorMap,会自动补全代码,如…

问题解决:VSCode Code Runner输出中文乱码

用vscode code runner插件经常会出现输出中文乱码的问题 那么如何解决这个问题呢? 解决方法: 通过设置,可以把代码放到 VS Code 内置的 Terminal 来运行,那么问题就能迎刃而解了。 选择 文件 -> 首选项 -> 设置&#xff0c…

VSCode——使用CodeRunner开发python输出中文出现乱码的解决方法

目录 方法一:每次都要加 方法二:一劳永逸 方法一 在程序中加入如下代码强行控制输出格式为utf-8即可 # -*- coding: utf-8 -*- import sys import io sys.stdoutio.TextIOWrapper(sys.stdout.buffer,encodingutf8)#改变默认输出的标准编码 编码改变…

修改CodeRunner快捷键

默认运行是 alt ctrl N, 改为alt ctrl r. run改更合理。 停止改为alt ctrl s stop也更合理🐻

vscode用CodeRunner运行C++文件,提示系统找不到路径

问题描述 如图,直接点运行出现如下提示: 但是分开执行还是正常的: 原因 cmd执行多条语句要使用" && “作为连接符,对应的powerShell要用” ; "。 解决 在用户设置中修改code-runner.executorMap中对应的值…

vscode_Code Runner(codeRunner)配置(自定义输入映射内容)/指定.c/.cpp编译选型/编译运行含有中文名的文件示例(最佳实践)

文章目录 声明:文件(目录)中包含中文的问题关于配置调试功能(推荐) 找到配置入口点击在setting.json中编辑可以指定各种语言的命令行映射,这里以c语言指定以c99编译修改为例.可见到,届时编译.c文件的时候就会是以C99的标准指定编译. 运行效果 声明:文件(目录)中包含中文的问题 …

用VSCode 编写C++ 引入自定义头文件容易犯的错 和CodeRunner配置

问题描述 undefined reference to log(char const*) collect2.exe: error: ld returned 1 exit status 原因分析: 一般是由于c编译时,.cpp源文件未找到引起的 解决方案: 1.正确操作 一.例如你一个文件下有一个.h的文件和一个.cpp的文件 在v…

VSCode用Code Runner编译运行c/c++

首先vscode需要在文件夹下打开,以下所有的文件都需要放到你编写程序文件夹下的.vscode文件夹里 平时可以右击文件夹,选择open with code,结构应该是这样的 配置CodeRunner 下载 左下角齿轮找到设置 在扩展中找到Run Code configuration,点击…

【Mac】VScode配置Python开发环境详细教程(报错解决Import Error No module named ) CodeRunner插件

文章目录 在VScode中安装python插件解决报错SyntaxError: Non-ASCII character \xef in file解决报错"No module named xxx "VScode上使用Jupyter Notebook的方法20.02.26 最新方法:Code Runner插件一键安装(python、java、C)终端目…

CodeRunner插件:自定义编译参数

在设置里找到Coderunner选项,然后找到executorMap 点击 在 settings.json 中编辑 ,你将看到对不同语言的编译指令。 以下为本人设置,为Mac系统,指令有所不同,不过操作流程是一样的。 "code-runner.executorMap…

java扩展包_CodeRunner 的 Java 扩展 Jar 包支持

CodeRunner 介绍 CodeRunner 是 Mac 上一款功能强大但使用简单代码工具,官方介绍支持几乎所有语言(20种语言),同时支持语法高亮、代码提示和多种界面主题,在学习新的语言或编写简单测试代码时非常实用。 我常常用它来管理一些代码片段和测试不…

vscode 中 coderunner无法运行dart程序

如果你的vscode使用了CodeRunner,运行dart文件出错找不到dart的情况下: /bin/sh: dart: command not found可以直接修改coderunner扩展配置信息, 将其路径改为你希望的位置