专栏文章
浅谈莫队的小细节
算法·理论参与者 8已保存评论 16
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 16 条
- 当前快照
- 1 份
- 快照标识符
- @miotkio3
- 此快照首次捕获于
- 2025/12/03 00:55 3 个月前
- 此快照最后确认于
- 2025/12/03 00:55 3 个月前
关于莫队
提起莫队,想必是很多人十分钟爱的暴力算法,毕竟在可以离线的部分区间询问问题当中,只需要花十分钟写一份简单的暴力,就可以获得 甚至是 的分数。
但是在学习简单的莫队的过程中,我也有一些问题以及自己的思考,写在这里分享给大家,希望对热爱或不热爱莫队的你都有帮助。
关于 的初始值
相信初学莫队,大家都对于 的写法十分不理解,为什么不能赋值成 呢?
在这里,我的理解是这样的(欢迎打脸)。
我们先来看莫队过程的代码,我的写法大体长这个样子:
CPPint l = 1,r = 0;
for(int i = 1;i <= m;i++)
{
int ll = q[i].l;
int rr = q[i].r;
while(l < ll) del(a[l]),l++;
while(l > ll) l--,add(a[l]);
while(r < rr) r++,add(a[r]);
while(r > rr) del(a[r]),r--;
ans[q[i].id] = sum;
}
我们考虑从初始状态需要依次将 移动到第一个询问处,在这个过程中,假设我们需要将 均向右移动,那么在这个过程中,我们需要执行下面语句:
CPP while(l < ll) del(a[l]),l++;
while(r < rr) r++,add(a[r]);
我们注意观察 位置对答案的贡献,当 时, 处贡献会由 减去一次,由 加上一次,不会产生影响。
而如果 ,就只有 减去一次贡献,而没有 加上一次贡献。同理,如果 ,那么就只有 加上一次贡献,而没有 减去,贡献都会错误。
当然,查询区间端点为 的时候自己考虑一下就不难发现时没有影响的。
因此,我们要将莫队初始化为 。
关于一些贡献的计算
接下来的问题,并不一定只在莫队中适用,只是恰好最近多次遇到了这个问题,就写在这里,也算是自己一点见解。
先来看个例题。
这道题目,首先一个显然的 trick 是,异或操作是可以类似前缀和去求的,那么我们也就是需要求出区间中有多少对 满足 。
我们考虑如何在移动的过程中加入和删除贡献,不难发现,只要我们将一个数对 的贡献记录在 或者 其中一个上即可。那么我们每次加入一个数 ,直接查询对应的 的数量,这样 的贡献只会在插入 时被计算到。
如果还是不太明白,我们以删除操作为例,详细讲解一下。
如果此时我们扫描到了一个数 ,想要删除他的贡献。
将 对应的 分为两类:已经被删除的和未被删除的。
对于已经被删除的,在删除 的时候我们会查询到 ,因此 的贡献已经在 处被删除过了。对于没有被删除的 ,我们查询这些 ,并删除贡献,然后从序列中删除 ,那么再删到 时也不会查询到 ,不会重复或遗漏。
所以在做查询数对的题目时,我们就不需要考虑莫队插入和删除的顺序,只需要在插入或删除一个数字的时候,在目前的序列中查询并记录贡献即可,这样可以保证贡献被记录在后加入或删除的元素上,不重不漏。
下面还有一道比较难的题目,也是希望大家知道前面统计贡献方法,嫌麻烦的朋友可以直接跳过。
这道题目只听了做法,还没有写代码。
具体做法大家可以参考 这位大神的题解。
最后统计部分,我们需要扫描线扫描 ,将 的合法 对统计贡献。
我们考虑将贡献统计在 或 其中一个,那么我们插入 时直接查询 ,插入 时直接查询 计算贡献即可,这样可以做到不重不漏。
这部分主要介绍的是统计数对贡献的一种思路,希望对大家有所帮助。
相关推荐
评论
共 16 条评论,欢迎与作者交流。
正在加载评论...