目录
-
普通莫队
-
莫队+bitset
-
回滚莫队
-
带修莫队
-
莫队二次离线
-
树上莫队
-
树上带修莫队
注:本文内 (n) (m) 在未说明情况下默认同阶。
普通莫队
莫队,离线算法,处理序列上的问题。
以分块为基本思想。
以左端点所在块为第一关键字排序,右端点其次。
块大小一般设为 (sqrt n)。
时间复杂度
对于某一块
左端点每次更新距离不超过 (sqrt n)。
右端点单调,端点移动总距离不超过 (n)。
对于两个块边界
左端点移动距离不超过 (2sqrt n)。
右端点同样不超过 (n)。
忽略常数后则无差别qwq。
那么综合来说,左端点 (n) 次 (sqrt n),右端点 (sqrt n) 次 (n)。
左右端点更新总时间皆为 (O(nsqrt n imes k))。((k) 为单次增量复杂度。)
所以这是莫队的时间复杂度。
莫队最终效果即在块中 左端抖动,右端单调。
一般写法
我们一般用 ( ext{for}) 里嵌四个 ( ext{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;
}
这样写可以减少右端点的移动,在常数上较优。
例题 [国家集训队]小Z的袜子
莫队裸题。
简单数学推导答案。
(ans=dfrac{sumlimits^n_{i=1}dfrac{cnt_i(cnt_i-1)}{2}}{dfrac{(r-l)(r-l+1)}{2}}) ((cnt_i) 表示在区间中 (i) 出现的次数。)(当然这个也能跑了)
可化为 (ans=dfrac{sumlimits^n_{i=1}cnt_i^2-(r-l+1)}{(r-l)(r-l+1)})
求最大公约数化简。
莫队维护出现次数平方之和即可。
例题 [Ynoi2015]盼君勿忘
详细题解戳这里。qwq
这里我们只阐述维护莫队的部分。
对于区间 ([l,r]),考虑 (a_i) 的贡献 。((l le i le r))
设 (a_i) 在区间 ([l,r]) 中出现 (cnt_{a_i}) 次。
我们知道,一个元素总数为 (k) 的集合的子集个数为 (2^k) 个。
可由 (sumlimits^k_{i=1}dbinom{k}{i}=2^k) 得出结论。
那么我们接下来算单个元素贡献有两种推导方法,这里我们讲一种比较方便的。
转换一下方向,一个子序列对于一个元素只有包含和不包含两种情况。
那么我们可以想到用总数减去不包含的状态数。
即直接可得 (2^{r-l+1}-2^{r-l+1-cnt_{a_i}})。
我们考虑在莫队时保存出现次数相同元素的和。
需要快速插入,删除。
由于不需要保证有序,容易想到双向链表。(建议手写)
(O(1)) 时间完成插入删除操作。
以上是莫队维护内容,关于取模+细节等可以看上面链接里的完整版题解。
莫队+bitset
bitset 常用于常规数据结构难以维护的的判定、统计问题,而莫队可以维护常规数据结构难以维护的区间信息。把两者结合起来使用可以同时利用两者的优势。——OI-Wiki
没有通用做法,因题而异。
例题 小清新人渣的本愿
基础题。
考虑如何维护莫队。
发现值域较小,考虑用 bitset 存储该值当前已有没有出现过,令其为 (s)。
对于减操作,有 (a-b=k)。
答案即为 ((s & (s<<k)).any())。
对于加操作,有 (a+b=k)。
即 (a-(maxn-b)=k-maxn)。
(maxn) 为值域上界。
则需要再开一个 bitset 储存 (maxn-x) 有没有出现过,令其为 (s')
答案即为 ((s & (s'<<(k-maxn))).any())
对于除操作,暴力枚举因数即可。
总复杂度 (((n+m)sqrt n+dfrac{nm}{w}))。 (这里把 加减操作和除操作都当 (m) 个了qaq)
例题 [Ynoi2016]掉进兔子洞
咕咕咕(
回滚莫队
显然,由名字得,回滚莫队还是要用莫队的基本思想。(
普通莫队看上面,以下不再赘述。
这种算法主要用于可离线查询,其中插入删除操作中一种方便一种复杂甚至不可做的情况。
既然一种简单一种麻烦,我们肯定选用简单的好qwq。
那么我们尽量全用简单的。
以下内容默认插入操作简单,反之类似。
排序方式与原先类似。
第一关键字 左端点所在块,第二关键字 右端点递增。
这样当左端点在同一块中,我们已经保证了右端点只有插入操作。
那么对于左端点,我们可以采取一个很暴力的操作——每次操作完回滚至当前块右端点,即从块右端点扩展到询问左端点,再将左端点还原回块右端点。
这是回滚莫队的核心操作。
当然我们还要考虑询问右端点在左端点同一块内的情况。
这种情况直接暴力跑即可,复杂度与拓展相同。
时间复杂度
由于块大小 (O(sqrt n)),一次暴力复杂度是 (O(sqrt n imes k)) ((k) 是一次增量所需时间)
同样对于莫队拓展我们知道是 (O(nsqrt n imes k))
(n) 次暴力复杂度也相等。
所以回滚莫队复杂度即 (O(nsqrt n imes k)),与莫队复杂度相同。
一般写法
个人比较喜欢这么写主程序。
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;
}
例题 【模板】回滚莫队&不删除莫队
板子题,但是下面那个更板子。(
首先套板子。(
然后离散化。(
增量时记录前后出现最远坐标即可。
对于本题,每次跑完一组块时,你还要清除痕迹。
建议拿一个东西记录已使用的位置,每次只需清理这些即可。
可以减小常数。
例题 歴史の研究
更加模板,甚至更好写。
你甚至不需要卡常。(
增量时每次维护 (T_A),并不断更新答案即可。
本题清空只需最后将右端点滚回当前左端点块的右端点并沿途还原 (T_A) 即可。
但是。
全部开 ( ext{int}) 会爆 (0)。(例 (10^5) 个 (10^9) )
全部开 ( ext{long long}) 即可。
带修莫队
普通莫队是不能带修改的。我们可以强行让它可以修改,就像 ( ext{DP}) 一样,可以强行加上一维 时间维 , 表示这次操作的时间。 ——OI WIKI
那么我们考虑一下时间复杂度。
时间复杂度
这里我们设块长为 (S)。
以左端点块为第一关键字,右端点块为第二关键字,时间为第三关键字。
对于左端点
移动复杂度显然 (O(nS))
对于右端点
对于一个左端点块时,有 (O(nS))。
左端点变换后最劣有 (O(dfrac{n^2}{S}))。
则为 (O(nS+dfrac{n^2}{S}))。
对于时间
每组左右块最多 (O(n))。
则总即为 (O(dfrac{n^3}{S^2}))。
删去常数合并起来即为 (O(nS+dfrac{n^2}{S}+dfrac{n^3}{S^2}))。
可得 (S=n^{frac{2}{3}}) 时近似最小。
总复杂度此时为 (O(n^{frac{5}{3}}))
写法
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;
}
例题 [国家集训队]数颜色 / 维护队列
例题 Machine Learning
咕
莫队二次离线
例题 【模板】莫队二次离线(第十四分块(前体))
例题 [Ynoi2019模拟赛]Yuno loves sqrt technology II
咕
树上莫队
例题 COT2 - Count on a tree II
例题 Tree and Queries
咕
树上带修莫队
例题 [WC2013]糖果公园
例题 [CTSC2008]网络管理
咕
咕咕咕