普通莫队学习笔记

莫队算法主要用于可以离线的区间询问回答。

引子

考虑一个这样的问题:假设没有事先求前缀和,你知道了数组第 5 个数到第 100 个数的和,现在询问问你第 4 个数到第 102 个数的和。怎么快速的计算?

显然直接暴力的去把第 4 个数加进去,然后把第 101 个数和 102 个数加进去。也就是说,当我们连续询问了两个问题,且这两个问题的交集比较大的时候,我们直接暴力的去处理不相交的部分,其实时间复杂度是很优秀的。

莫队就是这样一种处理询问的方式,通过排序,使得两两询问之间的交集尽可能的大。

如何排序

字母 含义
L 某一询问的左端点
R 某一询问的右端点
block 块长
N 数字个数
M 询问规模

如何排序使得区间交集尽可能大?

根据分块的思想,莫队按照 Lblock(设块长为 block)进行分组,然后每一组中的区间再按照 R 从小到大排序。

因为 L 被分了组,所以 L 端点移动复杂度不大于 block

对于相同组, R 从小到大移动不超过 N。因为总共有 Nblock 组, 所以总复杂度不超过 N2block ,均摊到每一次询问上就是 N2×blockM

NM 是一个数量级时,每次询问均摊的复杂度就是 block+Nblock。当 blockL 时,复杂度达到最小值,为 O(nn)

code

一次排序 + 四遍 while 循环即可

注意 l, r 移动区间时,++l--l++r--r 的顺序排列仅有 3 种排列是正确的,如下图(图源 OI-Wiki

img

口诀:先加后减,先左后右,先小后大

int n, block
struct query {
    int l, r, id;
    bool operator<(query x) {
        return l / block == x.l / block ? (r == x.r ? 0 : ((l / block) & 1) ^ (r < x.r)) : l < x.l;
    }
} q[N];

// update nowans
void move() {
    //...
}

void mo() {
    block = int(ceil(pow(n, 0.5)));
    sort(q + 1, q + n + 1);
    for (int i = 1; i <= n; ++i) {
        query Q = q[i];
        while (l < q.l) move(l++, 1);
        while (l > q.l) move(--l, 1);
        while (r < q.r) move(++r, 1);
        while (r > q.r) move(--r, 1);
    }
}

应用

区间 mex

首先莫队把询问离线排序后,我们可以很简单的维护出一个桶。

每次暴力维护移动的时候,就令 num[ l ] 加加或者减减。问题在于如何快速求 mex。在四个循环跑完以后,我们已经知道了当前区间形成的桶,怎样快速求出 mex 呢?

考虑对于桶进行分块。每 block 个桶合并出一个大的桶。这个桶存一下当前这个块内有多少元素被覆盖。

维护好这个大的桶后,我们就可以在 block+Nblock 时间复杂度内求得区间 mex

posted @   CKB2008  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示