【算法】莫队
莫队是永远也卡不住的。
—————— 许多dalao
很久以前 老师讲了莫队,目的是让我们理解分块思想,当时没时间做笔记(其实是懒的),现在来补上OVO。
使用范围(纯属个人理解)
区间求元素种类个数,每种种类元素个数……
一些看似线段树能做的题但实际上不满足加和性质的题。
大部分树状数组能做的题。
(如果有错误请大佬轻点打(\doge))
大概需要的东西
分块(我不会)
结构体排序(这个应该是得心应手了)
疯狂卡常小技巧(不一定需要)
一个清明的脑子和一双别老手残的手(这个很重要)
思路过程
先分块,再排序,最后四个 while 结束一切。
放个例题(老师的例题)
题意概括
有一个长度为 \(n\) 的序列,里面的元素为 \(c_i\) ,有 \(m\) 组询问,每次询问给出 \(l\) 和 \(r\),
求如果在 \([l,r]\) 里任意选两个数字选到相同数字的概率是多少,
输出答案以分数形式存在。
数据范围:\(n,m\in[1,50000]\),\(c_i,l,r\in[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 懂得)
膜拜大佬 %%%