【算法】莫队

莫队是永远也卡不住的。
                         —————— 许多dalao

很久以前 老师讲了莫队,目的是让我们理解分块思想,当时没时间做笔记(其实是懒的),现在来补上OVO。

使用范围(纯属个人理解)

  1. 区间求元素种类个数,每种种类元素个数……

  2. 一些看似线段树能做的题但实际上不满足加和性质的题。

  3. 大部分树状数组能做的题。

(如果有错误请大佬轻点打(doge))

大概需要的东西

  1. 分块(我不会)

  2. 结构体排序(这个应该是得心应手了)

  3. 疯狂卡常小技巧(不一定需要)

  4. 一个清明的脑子和一双别老手残的手(这个很重要)

思路过程

先分块,再排序,最后四个 while 结束一切。

放个例题(老师的例题)

洛谷 P1494 [国家集训队]小Z的袜子

题意概括

有一个长度为 (n) 的序列,里面的元素为 (c_i) ,有 (m) 组询问,每次询问给出 (l)(r)

求如果在 ([l,r]) 里任意选两个数字选到相同数字的概率是多少,

输出答案以分数形式存在。

数据范围:(n,min[1,50000])(c_i,l,rin[1,n])

分析

很明显,这道题符合之前我说的莫队适用情况。

这时候,就是莫队发威的时候了!!(雾)

注意:以下过程可能会让人不明不白的,但我会在后面细讲的。

First

以每个块为 (sqrt n) 的大小分成 (sqrt n) 个块 。

Second

以每个查询区间的左端点所在块顺序排序,

如果左端点所在块相同,就按照右端点所在块的序数奇偶性排序:

若右端点在奇数块,顺序排序;

若右端点在偶数块,倒叙排序。

Third

开始遍历所有查询区间,设 (l)(r) 两个指针作为遍历到的区间,

初始 (l = 1)(r = 0) 代表现在所在区间不存在,

然后从排好序的查询区间作为询问区间 (ql)(qr)

如果 (l < ql),就让 (l) 向右移动直到与 (ql) 重合;

如果 (l > ql),就让 (l) 向左移动直到与 (ql) 重合;

如果 (r < qr),就让 (r) 向右移动直到与 (qr) 重合;

如果 (r > qr),就让 (r) 向左移动直到与 (qr) 重合,

同时让记录 (l)(r) 之间所有种类元素的个数的数组 (cnt) 随遍历到的数字来加减计数。

Last

最后求出概率的分母与分子,约分后全部输出。

解释

Third

先说第三步,便于下面的解释。

一开始,还没有遍历的时候,(l)(r) 是初始化的,大概是这样:

请不要在意奇怪的数字

很明显,此时 (l)(r) 之间没有任何元素 。

我们假定 (ql = 2)(qr = 6)

而此时 (l) 小于 (ql),所以 (l) 向右移动直到与 (ql) 重合 。

现在是这样的:

关于更新 (cnt) 数组

每次扫到一个数字(无论是 (l) 还是 (r)),

如果之前扫到过,就说明 (cnt) 数组已经记录下来了这个元素,而现在再扫到说明要将这个元素从 (l)(r) 这个区间中删去,这时候 (cnt--)

如果之前没有扫到过,就说明 (cnt) 数组没有记录下来,而现在扫到说明要将这个元素从 (l)(r) 这个区间中加上,这时候 (cnt++)

现在继续模拟:

既然 (l)(ql) 已经重合了,那么现在需要更新的是 (r) 的位置,于是 (r) 开始向 (qr) 靠拢直到重合:

于是现在 (cnt) 数组里存的是 (ql)(qr) 的统计值,剩下的只需要在统计的时候按照求概率的公式来就行了,记得用 (ans) 数组记录下当前值,

毕竟查询下一个区间是在当前区间的基础下完成的。

First

分块不必多说,如果不懂就看看 这个

分块的作用大概就是让上面说的两个指针 (l)(r) 每次不会移动太大的距离:

(r) 从头到尾一直是从左到右的,而 (l) 则是在一个块里来回移动咣当。

Second

莫队的奇偶性排序简直是最玄学的,经老师的一番解释,我大约明白了是怎么回事。

但我说不出来……

不过这不重要,最重要的是知道要这么做就好啦(护头,大佬轻点打/doge)

Last

个人 jiao 得,这个才是最难得(确信脸),如果不会请看大佬们的题解(这个不属于莫队里的)

最后

如果看到这里还不懂的话,就看看这篇吧,

反正我是看这篇才会的(老师讲课永远 emmmm 懂得)

膜拜大佬 %%%

原文地址:https://www.cnblogs.com/EiffelA-blog/p/14130197.html