普通莫队算法
形式
假设
解释
离线后排序,顺序处理每个询问,暴力从上一个区间的答案转移到下一个区间答案(一步一步移动即可)。
排序方法
对于区间
实现
复杂度分析
以下的情况在
首先是分块这一步,这一步的时间复杂度是
接着就到了莫队算法的精髓了,下面我们用通俗易懂的初中方法来证明它的时间复杂度是
证:令每一块中
由第一次排序可知,
显然,对于每一块暴力求出第一个询问的时间复杂度为
考虑最坏的情况,在每一块中,
考虑
重点分析
将每一块
对于
(裂项求和)
由题可知
综上所述,莫队算法的时间复杂度为
但是对于
怎么分块呢?
我们设块长度为
事实上,如果块长度的设定不准确,则莫队的时间复杂度会受到很大影响。例如,如果
莫队算法看起来十分暴力,很大程度上是因为莫队算法的分块排序方法看起来很粗糙。我们会想到通过看上去更精细的排序方法对所有区间排序。一种方法是把所有区间
假设
莫队算法还有一个特点:当
例题 & 代码
题目大意:
有一个长度为
过程
思路:莫队算法模板题。
对于区间
然后从序列的第一个询问开始计算答案,第一个询问通过直接暴力算出,复杂度为
具体做法:
对于区间
我们设
而这个询问的答案就是
这里有个优化:
所以
所以
算法总复杂度:
下面的代码中 deno
表示答案的分母 (denominator),nume
表示分子 (numerator),sqn
表示块的大小:arr
是输入的数组,node
是存储询问的结构体,tab
是询问序列(排序后的),col
同上所述。
注意:由于 ++l
和 --r
的存在,下面代码中的移动区间的 4 个 while 循环的位置很关键,不能随意改变它们之间的位置关系。
莫队区间的移动过程,就相当于加入了
-
对于
的情况, 的元素相当于被加入了一次又被删除了一次, 的元素被加入一次, 的元素没有被加入。这个区间是合法区间。 -
对于
的情况, 的元素相当于被加入了一次又被删除了一次, 的元素没有被加入。这时这个区间表示空区间。 -
对于
的情况,那么 (这个区间非空)的元素被删除了一次但没有被加入,因此这个元素被加入的次数是负数。
因此,如果某时刻出现 set
维护区间中的所有数,就会出现“需要删除 set
中不存在的元素”的问题。
代码中的四个 while 循环一共有
循环顺序 | 正确性 | 反例或注释 |
---|---|---|
l--,l++,r--,r++ | 错误 | |
l--,l++,r++,r-- | 错误 | |
l--,r--,l++,r++ | 错误 | |
l--,r--,r++,l++ | 正确 | 证明较繁琐 |
l--,r++,l++,r-- | 正确 | |
l--,r++,r--,l++ | 正确 | |
l++,l--,r--,r++ | 错误 | |
l++,l--,r++,r-- | 错误 | |
l++,r++,l--,r-- | 错误 | |
l++,r++,r--,l-- | 错误 | |
l++,r--,l--,r++ | 错误 | |
l++,r--,r++,l-- | 错误 |
全部 24 种排列中只有 6 种是正确的,其中有 2 种的证明较繁琐,这里只给出其中 4 种的证明。
这 4 种正确写法的共同特点是,前两步先扩大区间(l--
或 r++
),后两步再缩小区间(l++
或 r--
)。这样写,前两步是扩大区间,可以保持
实现
普通莫队的优化
过程
我们看一下下面这组数据
// 设块的大小为 2 (假设)
1 1
2 100
3 1
4 100
手动模拟一下可以发现,r 指针的移动次数大概为 300 次,我们处理完第一个块之后,
什么是奇偶化排序?奇偶化排序即对于属于奇数块的询问,r 按从小到大排序,对于属于偶数块的排序,r 从大到小排序,这样我们的 r 指针在处理完这个奇数块的问题后,将在返回的途中处理偶数块的问题,再向 n 移动处理下一个奇数块的问题,优化了 r 指针的移动次数,一般情况下,这种优化能让程序快 30% 左右。
实现
排序代码:
压行
不压行
小细节:如果使用 sort 比较两个函数,不能出现
对于压行版,如果没有 r == x.r
的特判,当 l 属于同一奇数块且 r 相等时,会出现上面小细节中的问题(自己手动模拟一下),对于压行版,如果写成小于(大于)等于,则也会出现同样的问题。
参考资料
贡献者:@Jimmy@Menci@Ir1d@kenlig@mgt@Leo@Sweetlemon@Shuhao@Chrogeek@countercurrent_time@Zhikai@Thallium@Douglas@ouuan@akakw1@MingqiHuang@心旷神怡@Xeonacid@cesonic@Guanghao@konnyakuxzy
本页面最近更新:2/3/2023, 12:00:00 AM,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用