回滚莫队
介绍
在一些用莫队处理的问题中,扩展区间容易但收缩区间不好处理,这时可以考虑使用回滚莫队的技巧,以达到区间只增不减的效果。
具体步骤
对询问离线并排序,以左端点所在块号为第一关键字,右端点大小为第二关键字进行排序
以块号递增的顺序处理询问,每次只考虑左端点在该块内的询问
暴力回答所有左右端点都在块内的询问
记当前块号为 $k$,区间为 $[st[k],ed[k]]$,考虑剩下的询问,显然询问的右端点都大于 $ed[k]$ 并且递增
记当前已求得答案的区间为 $[l,r]$,答案为 $pre$,若该询问是该块内的第一次询问则初始化区间为 $[l=ed[k]+1, r=ed[k]]$
扩展 $r$ 至当前询问的右边界,并更新答案 $pre$
建立临时变量 $ans=pre$,扩展 $l$ 至当前询问的左边界,并更新答案 $ans$,扩展完毕后用 $ans$ 回答当前询问
回撤左端点 $l$ 至 $ed[k]+1$,并撤销扩展 $l$ 时对辅助变量的更改,保留 $pre$ 用于下次询问(即回撤区间至 $[ed[k],r]$)
这样我们就实现了区间只增不减的莫队,步骤 8 中撤销左边界的具体实现视所用辅助变量而定,例如桶的话就 $cnt[value]—$,并查集的话用按秩合以支持撤销操作。
分析下复杂度,假定 $n$ 和 $m$ 同数量级,取块大小 $B = \sqrt{n}$,暴力处理块内询问 $O(\sqrt{n})$,每个块内右端点至多扩展 $n$ 次 $O(n\sqrt{n})$,左端点每次最多扩展和撤销 $\sqrt{n}$ 次,因此总复杂度仍为 $O(n\sqrt{n})$。
模板
扩展右端点:cnt[x]++,pre=max(pre,cnt[x]*x)
扩展右端点:cnt[x]++,ans=max(ans,cnt[x]*x)
回撤左端点:cnt[x]--
1 | const int SZ = sqrt(MAXN); |
练习
Luogu P5906 【模板】回滚莫队&不删除莫队
扩展右端点再更新答案的同时记录每个数的最右出现位置,扩展左端点时用区间内最靠右的位置更新答案。
1 | const int SZ = sqrt(MAXN); |
ICPC2017北京C
显然扩展区间时用并查集可以很简单的维护答案,实现并查集撤销后应用回滚莫队即可
这题有点玄学,理论上来说应该按度数分块,因为扩展区间的复杂度并不是 $O(1)$ 的,而是取决于该点向区间内的连边数。那么应该保证块内总度数在 $\sqrt{m}$ 的数量级内。。。但这么分块会 TLE,估计数据的连边都是随机的,每个块内的边数都在 $\sqrt{n}$ 左右吧。
1 | const int B = sqrt(50000); |