【算法笔记】离线处理2——初探莫队

【例题Part.1】

洛谷 P4137 Rmq Problem / mex

https://www.luogu.com.cn/problem/P4137

经典mex问题,也可以离线搞

开一颗权值线段树,每一个叶子节点记录它对应的权值最后一次出现的位置(如果没有出现过则记为0),线段树维护区间最小值。

查询按照右端点升序排序,回答右端点为r的查询时,权值线段树里面记录的值是[1,r]区间内每个数最后一次出现的位置

回答查询[l,r]时在线段树里面二分答案区间(如果左子区间的值<l,就进入左子树,否则进入右子树)

这个方法和上一篇 离线处理1 的方法差不多,算是做一个回顾吧。顺便解锁了一个权值线段树的新用法。

当然,你也可以把这个权值线段树改成 可持久化的,就可以在线解决查询了。

个人认为可持久化权值线段树不等于主席树,虽然“主席树”这个概念不是很清楚,但我对它的定义是“在可持久化权值线段树的时间轴上做前缀和的一种方法”

 

既然讲查询的离线处理,怎能不讲莫队呢?

 

莫队是一种对多次查询进行离线处理的方法,主要思路就是从一个查询“爬”到另一个查询。

以区间查询为例,对于两个查询q1=[l1,lr] q2=[l2,r2],已知ans(q1),我们每一次做如下操作其中的一个

l1++

l1--

r1++

r1--

进行若干次操作后,l1=l2  r1=r2

每一次操作的同时,都维护有关信息,保证在爬到l1=l2  r1=r2的位置后,可以在短时间内查询ans(q2)

 

每一次都要一步一步地爬,难难道不会TLE吗?

我们可以把所有的查询按照一个特定的顺序排序,然后依次解决。

具体而言就是把长度为n的序列分块,每块长度大约是sqrt(n)

把所有查询按照左端点所在块为第一关键字(升序),右端点的位置为第二关键字(升序)排序,查询时就从上一个查询爬到下一个就好了。

为什么这样排序可以保证时间复杂度呢?

让我们胡乱分析一下:对于左端点在同一个块的查询,右端点滑动O(n)次就可以解决左端点在一个块内的查询,一共有O(sqrt(n))个块,所以右端点滑动          O(n sqrt(n))次。

再看左端点,最坏情况下,假设每一次都要从一个块的左端滑到右端,那么每一次查询就是O(sqrt(n)) 次滑动,一共n次查询(假设查询次数m和n同阶)

左端点的滑动次数也是O(n sqrt(n))的

综上所述,假设每次滑动的时间复杂度都是O(1)的,那么滑动的总时间复杂度就是O(n sqrt(n))的,限时1000ms的话,可以通过n<=5e5的数据

莫队其实很简单,只要可以找到O(1)的滑动,O(sqrt(n))以内的查询,就可以直接把模板改一下,轻松AC。

【例题】

https://www.luogu.com.cn/problem/P4462

上来就是紫题?

注意:k是固定的

如果你熟悉异或运算的特性,你就可以想到做一个异或前缀和(sum[i]=a[1]^a[2]^a[3]...a[n])

然后每一次查询q=[l,r]的实质就是 计数(a,b):sum[b]^sum[a]=k a,b∈[r,l-1],a<b

那么怎么爬呢?

用bkt[i]表示  计数k: k∈当前区间 bkt[k]=i

同时存下来当前区间的答案 ans

每次加入一个新的数a,ans+=bkt[k^a],然后更新bkt

删除同理,就是加入操作的逆操作

由于我们直接维护了答案的值,查询只需要O(1)的时间

下面的题目就会出现非直接维护答案的情况,也就是滑动到目标位置后,还要再做一个O(反正不是1)的查询

 

 

https://www.luogu.com.cn/problem/P3674

熟悉这位出题人的同学,应该会明白这又是一道毒瘤题

但这道题是很有意义的,它告诉我们std::bitset这个东西是多么的神(wei)奇(suo)

bitset单点修改O(1)的(很好的契合了莫队的特性),求count,位运算都是O(n/w)的,严格来说w是常数,时间复杂度还是O(n),然而这个w的大小相当可观,就是你的计算机的字长(就是64位,32位或16位)

现在计算机基本上都是64位的了,在开O2优化的情况下,可以很快地做位运算和求count

莫队里面用一个bitset维护区间内的每一位是否出现过,同时在维护一个倒序的bitset

求和为k的数对的个数,就是把一个bitset和它的逆序bitset位移一下做一个按位与 O(n/64)

求差为k的数对的个数,就是把一个bitset和它自己位移一下做一个按位与 O(n/64)

求积为k的数对的个数,就直接枚举约数,时间复杂度 O(sqrt(n))

卧槽,还有这种操作?

 

 

下一题可以值域分块来维护莫队,O(1)滑动,O(sqrt(n))查询

如果你想不到的话,不要紧,你已经学会了bitset+莫队 的赖皮算法了,开个O2就AC了

虽然可以bitset乱搞,但还是要介绍一下值域分块的做法,个人认为O(1)单点修改 O(sqrt(n))查询的分块和莫队的需求简直是完美契合

 

posted @ 2021-05-04 17:15  LMXZ  阅读(101)  评论(0)    收藏  举报