莫队 优雅暴力出奇迹

目录

  • 普通莫队

  • 莫队+bitset

  • 回滚莫队

  • 带修莫队

  • 莫队二次离线

  • 树上莫队

  • 树上带修莫队

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

普通莫队

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

分块为基本思想。

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

块大小一般设为 \(\sqrt n\)

时间复杂度

对于某一块

左端点每次更新距离不超过 \(\sqrt n\)

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

对于两个块边界

左端点移动距离不超过 \(2\sqrt n\)

右端点同样不超过 \(n\)

忽略常数后则无差别qwq。


那么综合来说,左端点 \(n\)\(\sqrt n\),右端点 \(\sqrt n\)\(n\)

左右端点更新总时间皆为 \(O(n\sqrt n \times k)\)。(\(k\) 为单次增量复杂度。)

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

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


一般写法

我们一般用 \(\text{for}\) 里嵌四个 \(\text{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{\sum\limits^n_{i=1}\dfrac{cnt_i(cnt_i-1)}{2}}{\dfrac{(r-l)(r-l+1)}{2}}\)\(cnt_i\) 表示在区间中 \(i\) 出现的次数。)(当然这个也能跑了)

可化为 \(ans=\dfrac{\sum\limits^n_{i=1}cnt_i^2-(r-l+1)}{(r-l)(r-l+1)}\)

求最大公约数化简。

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

代码qwq


例题 [Ynoi2015]盼君勿忘

详细题解戳这里。qwq

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

对于区间 \([l,r]\),考虑 \(a_i\) 的贡献 。(\(l \le i \le r\)

\(a_i\) 在区间 \([l,r]\) 中出现 \(cnt_{a_i}\) 次。

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

可由 \(\sum\limits^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)

代码qwq

例题 [Ynoi2016]掉进兔子洞

咕咕咕(


回滚莫队

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

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

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

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

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

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

排序方式与原先类似。

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

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

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

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

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

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

时间复杂度

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

同样对于莫队拓展我们知道是 \(O(n\sqrt n \times k)\)

\(n\) 次暴力复杂度也相等。

所以回滚莫队复杂度即 \(O(n\sqrt n\times 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;
}

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

具体题解qwq

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

首先套板子。(

然后离散化。(

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

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

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

可以减小常数。


例题 歴史の研究

完整题解qwq

更加模板,甚至更好写。

你甚至不需要卡常。(

增量时每次维护 \(T_A\),并不断更新答案即可。

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

但是。

全部开 \(\text{int}\) 会爆 \(0\)。(例 \(10^5\)\(10^9\)

全部开 \(\text{long long}\) 即可。


带修莫队

普通莫队是不能带修改的。我们可以强行让它可以修改,就像 \(\text{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]网络管理

咕咕咕

posted @ 2020-10-18 10:36  Demoe  阅读(184)  评论(0编辑  收藏  举报