莫队算法+带修莫队+回滚莫队

莫队算法

本质上。。似乎是大暴力。。。
##传说中能解决一切区间问题的算法
如果我们**知道区间 [ L , R ] [L,R] [L,R],就能在比较短的时间内求出 [ 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]**的话,那就可以用莫队算法了。

有一种经典的问题:给你一些不带修改的区间询问,要求快速回答

显然,有一些我们可以通过线段树来完成,因为线段树是 O ( N l o g N ) O(NlogN) O(NlogN)

但是,线段树有的东西是维护不了的。

看一个例子

给你一个数列,若干询问,要求回答区间内同种颜色的数量。

线段树很难做,怎么办?

用莫队算法!

莫队算法的实质是通过将询问排序,每个询问均由前一个询问(排序后的)转移得来,通过一定的排序优化时间复杂度。往往可以有 O ( N N ) O(N\sqrt N) O(NN )的效果

回到题目
显然对于两次询问 L , R L,R L,R L ′ , R ′ L',R' L,R,知道了 L , R L,R L,R的答案,就可以暴力计算 ∣ L − L ′ ∣ + ∣ R − R ′ ∣ |L-L'|+|R-R'| LL+RR次得出 L ′ , R ′ L',R' L,R的答案。

∣ L − L ′ ∣ + ∣ R − R ′ ∣ |L-L'|+|R-R'| LL+RR。也就是曼哈顿距离

把每个询问看作是二维平面上的点,那么我们的最小总时间,就是这些点的最小曼哈顿距离生成树, 按照这个树的顺序做,复杂度变成了 O ( N N ) O(N\sqrt N) O(NN )(为什么?),而且这个生成树连边也有特别的技巧,可以去看莫队在知乎上推荐的那篇。https://zhuanlan.zhihu.com/p/25017840

然而这样有一点猥琐

有一个优美方便简洁好理解的替代品

分块大法好!

把整个序列分块,把 L L L按照所在块的顺序为第一关键字,把 R R R R R R本身!)为第二关键字排序。

为什么要分块不能直接排呢?

分块很好的减少了一种情况的影响。

L &lt; L ′ &lt; L ′ ′ L&lt;L&#x27;&lt;L&#x27;&#x27; L<L<L,并且距离很近,但是 R ′ &lt; R &lt; R ′ ′ R&#x27;&lt;R&lt;R&#x27;&#x27; R<R<R,并且 R ′ R&#x27; R与另外两个离得很远。如果直接按 L L L排序就会浪费非常多的时间。

由于每个块的大小是 N \sqrt N N

分块使得两个询问之间差异平均到了 L , R L,R L,R上。

因此,理论复杂度大约是 O ( N N ) O(N\sqrt N) O(NN ),实际上是 O ( O( O(玄学??跑的过就行 ) ) )(假设询问数与序列长度同阶)

带上了修改,怎么办?

然后我们继续分块。

把二元组 L , R L,R L,R变成三元组 L , R , x L,R,x L,R,x x x x是这次询问在修改第几次后。

然后把 R R R所在块看作第二关键字, x x x看作第三关键字排序即可。

转移时直接恢复(或删除)两次询问之间的修改。如果在区间内还要计算对答案的影响

然而修改的复杂度不同

最优分块方式是每块 N 2 3 N^{2\over 3} N32,总共 N 1 3 N^{1\over 3} N31
左指针移动 N ∗ N 2 3 = N 5 3 N*N^{2\over 3}=N^{5 \over 3} NN32=N35
右指针移动 N ∗ N 2 3 = N 5 3 N*N^{2\over 3}=N^{5 \over 3} NN32=N35
修改指针移动 ( N 1 3 ) 2 ∗ N = N 5 3 {(N^{1\over 3})}^2*N=N^{5 \over 3} (N31)2N=N35

总复杂度 N 5 3 N^{5 \over 3} N35

在实际操作中,有时取一些什么 n ∗ 10 \sqrt n*10 n 10之类的奇葩的数可能更快

有一道板题
http://blog.csdn.net/hzj1054689699/article/details/51880644

回滚莫队(仅插入无需删除)

有时候我们需要处理一些特殊情况,例如维护区间最大最小值。
加入的时候我们很好处理,但删除就不知道该怎么办了。

此时就需要一些处理技巧。

我们仍然对于询问排序,将序列分块,第一关键字为左端点所在块的位置,第二关键字为右端点位置。

对于询问左端点在同一块的情况,右端点是有序的,但左端点可能在这块中是无序的,那么我们直接将左端点定在这一块的右端,由于询问右端点是单调递增的,右端点可以不断向后扩展,碰到一个询问,我们就将当前左端点暴力移到询问的左端点,计算答案,再暴力移回当前块的右端。

可能有疑惑:这不是一样需要删除吗?
其实不然,我们可以在左移前保存状态,计算完直接恢复。或者在每个位置记录在哪里修改了,原状态是什么,回滚的时候将修改撤销即可。

与普通莫队相比,每一个询问我们仅仅是左端点多移了不超过一个块的距离,因此总复杂度仍然是 m n m\sqrt n mn 的,非常优秀!

<think>嗯,用户想了解什么是。首先,我需要回忆莫队算法的基本概念。莫队算法是用于处理区间查询问题的离线算法,由涛提出,所以叫。 用户可能知道一些基础的算法,比如暴力法,但相比暴力有什么优势呢?对了,通过分块和排序来优化时间复杂度,将复杂度从O(n²)降到O(n√n)。需要解释清楚分块的思想,以及如何排序查询。 然后,的核心是处理多个区间查询,比如数组上的查询,如何通过调整左右指针来减少重复计算。应该举一个具体的例子,比如处理区间和或者区间内不同元素的数量,这样用户更容易理解。 用户可能想知道的适用场景。比如离线查询、不改的情况。如果有改的话,可能需要修莫队,但这是进阶内容,可能暂时不需要深入,但可以提一下扩展。 需要注意的实现步骤,包括分块大小、排序方式、移动指针的策略。可能还需要提到不同的变种,比如普通修莫队回滚,但主要介绍基础版本。 要确保语言通俗易懂,避免过多数学公式,但按照用户的要求,行内公式用$...$,独立公式用$$...$$。例如,分块的大小通常是√n,所以块的大小是$\sqrt{n}$,每个块处理查询的排序方式是按右端点递增或递减,根据块奇偶性决定,这样减少指针移动次数。 可能用户的应用场景是算法竞赛或者面试题,所以需要强调的应用实例,比如统计区间内不同数的个数,或者满足某些条件的子区间数目。可以举一个例子,比如查询区间[l, r]中不同数字的数量,用如何高效处理。 还要指出的优缺点。优点是优化时间复杂度,适用于离线查询;缺点是不支持在线,或者改操作处理起来复杂。可能还要比较其他方法,比如前缀和、线段树等,但在某些情况下更优。 最后,总结的基本思想,分块、排序、移动指针,以及适用的问题类型。确保回答结构清晰,逐步引导用户理解的原理和应用。</think>莫队算法(Mo's Algorithm)是一种用于高效处理**离线区间查询问题**的算法,由涛(Tao Mo)于2009年提出。它通过巧妙的分块和排序策略,将暴力算法的$O(n^2)$时间复杂度优化至$O(n\sqrt{n})$,适用于多次区间查询的场景。 --- ### **核心思想** 1. **分块处理** 将长度为$n$的序列分成$\sqrt{n}$个块,每个块大小为$\sqrt{n}$。例如,当$n=16$时,分成4个块,每块4个元素。 2. **排序查询** 对所有查询区间$[L_i, R_i]$按以下规则排序: - 按**左端点$L_i$所在的块号**升序排列。 - 若左端点在同一块,则按**右端点$R_i$升序**(偶块)或**降序**(奇块)排列,以减少指针移动次数。 3. **双指针滑动** 维护当前区间$[curL, curR]$,通过逐步移动左右指针处理每个查询,利用相邻查询的区间重叠部分避免重复计算。 --- ### **算法步骤** 1. 初始化当前区间$curL=0$, $curR=-1$(空区间)。 2. 按排序后的顺序遍历所有查询: - **扩展区间**:移动$curL$和$curR$至目标区间$[L_i, R_i]$。 - **记录结果**:在移动过程中统计所需信息(如元素出现次数、最大值等)。 --- ### **示例:统计区间内不同数字的数量** 假设序列为$[1, 3, 2, 3, 1]$,查询区间$[1,3]$(元素为$3,2,3$),不同数字有$2$个(3和2)。 **处理流程**: 1. 分块:块大小为$\sqrt{5} \approx 2$,分为3块。 2. 排序查询:假设查询已排序为$[0,1]$, $[1,3]$, $[2,4]$。 3. 移动指针并统计: - 从空区间开始,向右扩展$curR$到1,统计数字1。 - 继续扩展$curR$到3,统计3、2、3,记录不同数字数量。 --- ### **时间复杂度分析** - 每块内左指针最多移动$\sqrt{n}$次,总移动次数为$O(q\sqrt{n})$($q$为查询数)。 - 右指针在同一块内单调移动,总移动次数为$O(n\sqrt{n})$。 - **整体复杂度**:$O((q + n)\sqrt{n})$,通常简化为$O(n\sqrt{n})$。 --- ### **适用场景** 1. **离线查询**:需预先知道所有查询。 2. **无改操作**:若需支持改,需使用修莫队(复杂度$O(n^{\frac{5}{3}})$)。 3. **典型问题**: - 区间内不同元素数量(如LeetCode 992)。 - 区间众数、区间逆序对数等。 --- ### **优缺点** - **优点**:代码简单,易于实现;在离线查询中效率显著高于暴力法。 - **缺点**:不支持在线查询;对含改的问题需额外处理。 --- ### **代码框架(以统计不同元素为例)** ```python block_size = int(n**0.5) queries = sorted(enumerate(queries), key=lambda x: (x[1][0]//block_size, x[1][1] if (x[1][0]//block_size)%2==0 else -x[1][1])) curL, curR = 0, -1 count = {} ans = [0] * q for idx, (L, R) in queries: while curL > L: curL -= 1 count[A[curL]] = count.get(A[curL], 0) + 1 while curR < R: curR += 1 count[A[curR]] = count.get(A[curR], 0) + 1 while curL < L: count[A[curL]] -= 1 if count[A[curL]] == 0: del count[A[curL]] curL += 1 while curR > R: count[A[curR]] -= 1 if count[A[curR]] == 0: del count[A[curR]] curR -= 1 ans[idx] = len(count) ``` --- 通过分块排序和指针移动,莫队算法将区间查询的复杂度显著降低,是解决离线区间问题的经典工具。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值