【莫队·进阶篇】二次离线莫队与区间逆序对问题

\(Preface\)

一种高端科技,在NOI2020Day1T3爆炸之后,不会\(A\)部分分(裸·区间逆序对)的余下定决心要好好学习根号算法。

二次离线莫队听名字就是个非常高级的东西,而且虽说是二次离线,但它的复杂度仍旧是\(O(n\sqrt n)\),非常神奇。

应用范围

一般用于移动左右端点时复杂度较大的莫队。

例如区间逆序对,它有一个非常显然的莫队+树状数组的\(O(n\sqrt nlogn)\)做法,原因就是每次移动端点时要在树状数组上询问。

而二次离线莫队可以完美解决这个问题。

以区间逆序对问题为例

假设当前左右端点为\(L,R\),现要将右端点向右移一位。

考虑这次移动之后,答案的变化值就是\([L,R]\)中大于\(a_{R+1}\)的数的个数。

这东西可以拆成两个:\([1,R]\)中大于\(a_{R+1}\)的数的个数减去\([1,L-1]\)中大于\(a_{R+1}\)的数的个数。

其中前面那个东西显然可以树状数组\(O(nlogn)\)预处理。

而后面这个东西,我们可以对于每个位置开一个\(vector\),然后把\(R+1\)连同当前询问的编号一同扔到\(L-1\)\(vector\)中。

根据莫队的复杂度证明,我们一共会移动端点\(O(n\sqrt n)\)次,这样的内存可能会有点大。

然而我们发现,假设一个询问需要我们把\(R\)向右移到\(R'\),移动过程中\(L\)是保持不动的,因此实际上我们可以直接把\([R+1,R']\)整个区间连同询问编号一起扔到\(L-1\)\(vector\)中。这样一来内存就是\(O(n)\)级别的了。

把移动区间扔到\(L-1\)\(vector\)中的过程,就是第二次离线。

然后我们只要从左到右枚举每一个位置,处理该位置\(vector\)中的询问。

注意虽然我们存的是区间,但询问时依旧要枚举区间中的每一个数一个个询问,询问次数依旧是\(O(n\sqrt n)\)的。

又由于我们只会枚举\(1\sim n\)\(n\)个位置,因此可以考虑适当增加修改的复杂度以实现\(O(1)\)询问。

于是就想到\(O(n\sqrt n)\)修改、\(O(1)\)询问的分块。

这样就彻底解决了整个问题。

具体实现过程(伪代码?)

具体实现中,由于左端点移动和右端点移动答案变化值询问范围相反,因此很多东西都要开两个。

  • 读入数据并进行一些简单处理(例如离散化)。
  • 预处理:\(t1_i,t2_i\)分别表示\(1\sim i-1\)中大于/小于\(i\)的数的个数,则用树状数组预处理出它俩的前缀和\(s1_i,s2_i\)。(\(t1_i,t2_i\)无需存储)
  • 第一次离线: 给询问排序,进行莫队。(\(g1[x],g2[x]\)为每个位置的两个\(vector\),四元组表示询问左/右端点、询问编号以及对答案贡献的符号)
    • \(R<r_i\):往\(g1[L-1]\)中扔入\((R+1,r_i,pos_i,-1)\)\(ans[pos_i]\)加上\(s1_{r_i}-s1_R\),然后令\(R=r_i\)
    • \(L>l_i\):往\(g2[R]\)中扔入\((l_i,L-1,pos_i,1)\)\(ans[pos_i]\)减去\(s2_{L-1}-s2_{l_i-1}\),然后令\(L=l_i\)
    • \(R>r_i\):往\(g1[L-1]\)中扔入\((R_i+1,R,pos_i,1)\)\(ans[pos_i]\)减去\(s1_R-s1_{r_i}\),然后令\(R=r_i\)
    • \(L<l_i\):往\(g2[R]\)中扔入\((L,l_i-1,pos_i,-1)\)\(ans[pos_i]\)加上\(s2_{l_i-1}-s2_{L-1}\),然后令\(L=l_i\)
  • 第二次离线: 枚举每一个位置,处理\(vector\)中的询问。
    • \(O(\sqrt n)\)加入\(a_i\)的贡献。
    • 枚举\(g1\)中的元素\((l,r,pos,op)\),枚举\(x\in[l,r]\),给\(ans[pos]\)加上\(op\times Q1(a_x+1)\)\(Q1(v)\)返回大于等于\(v\)的数的个数)。
    • 枚举\(g2\)中的元素\((l,r,pos,op)\),枚举\(x\in[l,r]\),给\(ans[pos]\)加上\(op\times Q2(a_x-1)\)\(Q2(v)\)返回小于等于\(v\)的数的个数)。
  • 最后,给答案做个前缀和(因为我们求出的只是答案变化值),然后输出。

时间复杂度分析

预处理(树状数组):\(O(nlogn)\)

第一次离线(莫队):\(O(n)\)(因为此时我们没必要再一点一点移动端点,直接一步到位就可以了)

第二次离线(分块):\(O(n\sqrt n)\)(修改单次复杂度\(O(\sqrt n)\),次数\(O(n)\);询问单次复杂度\(O(1)\),次数\(O(n\sqrt n)\)

总复杂度:\(O(n\sqrt n)\)

(如有不对请指出~~~)

\(Postscript\)

毒瘤lxl的题目让我感受到深深的恶意,果然NOI这种东西还不是余现在的水平能够挑战的。。。

posted @ 2020-08-21 13:50  童女讴歌的荣华帝政  阅读(724)  评论(0编辑  收藏  举报