莫队 优雅暴力出奇迹

1|0目录

  • 普通莫队

  • 莫队+bitset

  • 回滚莫队

  • 带修莫队

  • 莫队二次离线

  • 树上莫队

  • 树上带修莫队

注:本文内 n m 在未说明情况下默认同阶。

1|1普通莫队

莫队,离线算法,处理序列上的问题。

分块为基本思想。

以左端点所在块为第一关键字排序,右端点其次。

块大小一般设为 n

1|0时间复杂度

1|0对于某一块

左端点每次更新距离不超过 n

右端点单调,端点移动总距离不超过 n

1|0对于两个块边界

左端点移动距离不超过 2n

右端点同样不超过 n

忽略常数后则无差别qwq。


那么综合来说,左端点 nn,右端点 nn

左右端点更新总时间皆为 O(nn×k)。(k 为单次增量复杂度。)

所以这是莫队的时间复杂度。

莫队最终效果即在块中 左端抖动,右端单调


1|0一般写法

我们一般用 for 里嵌四个 while 来完成。

sort(q+1,q+m+1,cmp); for(re i=1;i<=m;i++){ ...... while(l>q[i].l) --l,upd(l,1); while(r<q[i].r) ++r,upd(r,1); while(l<q[i].l) upd(l,-1),l++; while(r>q[i].r) upd(r,-1),r--; ans[q[i].id]=...... }

对于排序,我比较喜欢按照下面这种写法写。

bool cmp(Question x,Question y){ if(x.pos!=y.pos) return x.pos<y.pos; if(x.pos&1) return x.r<y.r; return x.r>y.r; }

这样写可以减少右端点的移动,在常数上较优。


1|0例题 [国家集训队]小Z的袜子

莫队裸题。

简单数学推导答案。

ans=i=1ncnti(cnti1)2(rl)(rl+1)2cnti 表示在区间中 i 出现的次数。)(当然这个也能跑了)

可化为 ans=i=1ncnti2(rl+1)(rl)(rl+1)

求最大公约数化简。

莫队维护出现次数平方之和即可。

代码qwq


1|0例题 [Ynoi2015]盼君勿忘

详细题解戳这里。qwq

这里我们只阐述维护莫队的部分。

对于区间 [l,r],考虑 ai 的贡献 。(lir

ai 在区间 [l,r] 中出现 cntai 次。

我们知道,一个元素总数为 k 的集合的子集个数为 2k 个。

可由 i=1k(ki)=2k 得出结论。

那么我们接下来算单个元素贡献有两种推导方法,这里我们讲一种比较方便的。

转换一下方向,一个子序列对于一个元素只有包含和不包含两种情况。

那么我们可以想到用总数减去不包含的状态数。

即直接可得 2rl+12rl+1cntai


我们考虑在莫队时保存出现次数相同元素的和。

需要快速插入,删除。

由于不需要保证有序,容易想到双向链表。(建议手写)

O(1) 时间完成插入删除操作。

以上是莫队维护内容,关于取模+细节等可以看上面链接里的完整版题解。


1|2莫队+bitset

bitset 常用于常规数据结构难以维护的的判定、统计问题,而莫队可以维护常规数据结构难以维护的区间信息。把两者结合起来使用可以同时利用两者的优势。——OI-Wiki

没有通用做法,因题而异。

1|0例题 小清新人渣的本愿

基础题。

考虑如何维护莫队。

发现值域较小,考虑用 bitset 存储该值当前已有没有出现过,令其为 s

对于减操作,有 ab=k

答案即为 (s&(s<<k)).any()

对于加操作,有 a+b=k

a(maxnb)=kmaxn

maxn 为值域上界。

则需要再开一个 bitset 储存 maxnx 有没有出现过,令其为 s

答案即为 (s&(s<<(kmaxn))).any()

对于除操作,暴力枚举因数即可。

总复杂度 ((n+m)n+nmw)。 (这里把 加减操作和除操作都当 m 个了qaq)

代码qwq

1|0例题 [Ynoi2016]掉进兔子洞

咕咕咕(


1|3回滚莫队

显然,由名字得,回滚莫队还是要用莫队的基本思想。(

普通莫队看上面,以下不再赘述。

这种算法主要用于可离线查询,其中插入删除操作中一种方便一种复杂甚至不可做的情况。

既然一种简单一种麻烦,我们肯定选用简单的好qwq。

那么我们尽量全用简单的。

1|0以下内容默认插入操作简单,反之类似。

排序方式与原先类似。

第一关键字 左端点所在块,第二关键字 右端点递增。

这样当左端点在同一块中,我们已经保证了右端点只有插入操作。

那么对于左端点,我们可以采取一个很暴力的操作——每次操作完回滚至当前块右端点,即从块右端点扩展到询问左端点,再将左端点还原回块右端点

这是回滚莫队的核心操作。

当然我们还要考虑询问右端点在左端点同一块内的情况。

这种情况直接暴力跑即可,复杂度与拓展相同。

1|0时间复杂度

由于块大小 O(n),一次暴力复杂度是 O(n×k)k 是一次增量所需时间)

同样对于莫队拓展我们知道是 O(nn×k)

n 次暴力复杂度也相等。

所以回滚莫队复杂度即 O(nn×k),与莫队复杂度相同。


1|0一般写法

个人比较喜欢这么写主程序。

for(int i=1,j=1;j<=(n-1)/len+1;++j){ for(int k=1;k<=cnt;++k) lst[a[vst[k]]]=nxt[a[vst[k]]]=0; int br=min(j*len,n);l=br+1,r=br,sum=cnt=0; while(q[i].pos==j){ if(q[i].r<=br){ ans[q[i].id]=solve(q[i].l,q[i].r);++i; continue; } while(r<q[i].r) ++r,update(r,1); qwq=sum; while(l>q[i].l) --l,update(l,-1); ans[q[i].id]=sum; while(l<=br) erase(l),l++; sum=qwq;++i; } }

排序函数基本不变,将常数优化部分删去即可。(注意 若删除简单时右端点应降序

bool cmp(Question x,Question y){ if(x.pos!=y.pos) return x.pos<y.pos; return x.r<y.r; }

1|0例题 【模板】回滚莫队&不删除莫队

具体题解qwq

板子题,但是下面那个更板子。(

首先套板子。(

然后离散化。(

增量时记录前后出现最远坐标即可。

对于本题,每次跑完一组块时,你还要清除痕迹。

建议拿一个东西记录已使用的位置,每次只需清理这些即可。

可以减小常数。


1|0例题 歴史の研究

完整题解qwq

更加模板,甚至更好写。

1|0你甚至不需要卡常。(

增量时每次维护 TA,并不断更新答案即可。

本题清空只需最后将右端点滚回当前左端点块的右端点并沿途还原 TA 即可。

但是。

全部开 int 会爆 0。(例 105109

全部开 long long 即可。


1|4带修莫队

普通莫队是不能带修改的。我们可以强行让它可以修改,就像 DP 一样,可以强行加上一维 时间维 , 表示这次操作的时间。 ——OI WIKI

那么我们考虑一下时间复杂度

1|0时间复杂度

这里我们设块长为 S

以左端点块为第一关键字,右端点为第二关键字,时间为第三关键字。

1|0对于左端点

移动复杂度显然 O(nS)

1|0对于右端点

对于一个左端点块时,有 O(nS)

左端点变换后最劣有 O(n2S)

则为 O(nS+n2S)

1|0对于时间

每组左右块最多 O(n)

则总即为 O(n3S2)

删去常数合并起来即为 O(nS+n2S+n3S2)

可得 S=n23 时近似最小。

总复杂度此时为 O(n53)

1|0写法

inline void update(int x,int op){ //端点修 if(op==1) sum+=(cnt[a[x]]++==0); else sum-=(--cnt[a[x]]==0); } inline void modify(int x,int op){ //时间修 if(mo[x].x>=l&&mo[x].x<=r){ if(op==1){ sum-=(--cnt[a[mo[x].x]]==0); mo[x].pre=a[mo[x].x];a[mo[x].x]=mo[x].to; sum+=(cnt[a[mo[x].x]]++==0); } else{ sum-=(--cnt[a[mo[x].x]]==0); a[mo[x].x]=mo[x].pre; sum+=(cnt[a[mo[x].x]]++==0); } } else{ if(op==1) mo[x].pre=a[mo[x].x],a[mo[x].x]=mo[x].to; else a[mo[x].x]=mo[x].pre; } }
for(re i=1;i<=qwq;i++){ while(l>q[i].l) --l,update(l,1); while(r<q[i].r) ++r,update(r,1); while(l<q[i].l) update(l,-1),l++; while(r>q[i].r) update(r,-1),r--; while(nw<q[i].t) ++nw,modify(nw,1); while(nw>q[i].t) modify(nw,-1),nw--; ans[q[i].id]=sum; }

1|0例题 [国家集训队]数颜色 / 维护队列

1|0例题 Machine Learning

1|5莫队二次离线

1|0例题 【模板】莫队二次离线(第十四分块(前体))

1|0例题 [Ynoi2019模拟赛]Yuno loves sqrt technology II

1|6树上莫队

1|0例题 COT2 - Count on a tree II

1|0例题 Tree and Queries

1|7树上带修莫队

1|0例题 [WC2013]糖果公园

1|0例题 [CTSC2008]网络管理

咕咕咕


__EOF__

本文作者Daniel Jiang
本文链接https://www.cnblogs.com/danieljiang/p/mo_algorithm.html
关于博主:JSOIer 高一萌新 求带/kel
版权声明:awa
声援博主:求赞/kel
posted @   Demoe  阅读(187)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· .NET Core 中如何实现缓存的预热?
· 三行代码完成国际化适配,妙~啊~
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
点击右上角即可分享
微信分享提示