2025.2.27 闲话:普通莫队的正确与错误写法
远古错误排序
long long
ago,我以为莫队就是把区间询问按照左端点为第一关键字、右端点为第二关键字排序,这样移动端点就可以做到 \(O(n\sqrt{n})\) 的时间复杂度了。事实上,如果出题人不刻意卡的话,复杂度也确实差不多是这样。然而,这个方法是可以卡的。
首先,左端点是单调的,移动次数为 \(O(n)\),这个没毛病。
但是,对于右端点,我们有方法让它多次大跳,即构造形如以下数据:
[1,n] [2,3] [3,n] [4,5] [5,n] ...
这样,每走一次右端点都要移动约 \(n-l\) 次,全部加起来用等差数列求和公式一算,好家伙卡成 \(O(n^2)\)。
正确排序
将整个数列分为 \(\sqrt{n}\) 块,左端点在同一块内的按照右端点排序,不在的按照左端点(所在块编号)排序。这样,我们就将所有询问分为了 \(\sqrt{n}\) 组,每组内右端点单调,两个左端点距离不超过 \(\sqrt{n}\)。
在这样的数列上移动,\(n\) 个左端点每个最多移动 \(\sqrt{n}\) 即可转移到下一个询问区间;\(\sqrt{n}\) 组每组内右端点一共最多移动 \(n\) 次;共 \(\sqrt{n}\) 次跨块移动,每次最多移动 \(n\) 次。总时间复杂度 \(O(n\sqrt{n})\)。
最优块长
上面按照一般分块的想法把块长设定为 \(\sqrt{n}\),但这样某些时候是不优的。
假设数列长为 \(n\),询问数量为 \(m\)。设块长为 \(S\),那么整个数列将被分为 \(\frac{n}{S}\) 块。
- 左端点:每个左端点最多移动 \(S\) 次,一共最多 \(mS\) 次;
- 右端点:每块内最多移动 \(n\) 次,数列有 \(\frac{n}{S}\) 块,所以一共最多移动 \(\frac{n^2}{S}\) 次。
- 跨块移动:共有 \(\frac{n}{S}\) 次这样的移动,每次最多移动 \(n\) 次,所以最多也是 \(\frac{n^2}{S}\) 次。
因此,时间复杂度为 \(O(\frac{n^2}{S}+mS)\),高一不等式知识可得 \(\frac{n^2}{S}+mS \ge 2\sqrt{\frac{n^2}{S} \times mS} = 2n\sqrt{m}\),即最优时间复杂度为 \(O(n\sqrt{m})\)。此时 \(\frac{n^2}{S}=mS\),解得 \(S=n\sqrt{m}\),这就是最优块长。
莫队移动顺序
记住一条就行了:先扩大区间再缩小区间。
这一条我似乎很早以前就发现了,但是现在才看到详细证明。
参考资料
本文采用 「CC-BY-NC 4.0」 创作共享协议,转载请注明作者及出处,禁止商业使用。
作者:Jerrycyx,原文链接:https://www.cnblogs.com/jerrycyx/p/18755597
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步