莫队算法优化
1.奇偶优化
众所周知,同一块内的询问右边界时单调递增的。此时 \(r\) 指针可以一路向右移动,于是有较高的效率。
但是当询问来到下一个块时, \(r\) 指针又会回调至此时最左侧的询问右边界,且回调中不会有任何操作。
那么考虑利用这段回调。我们把当前块内询问按右边界递减排序,从上一个块回调时就可以直接计算;下一个块按右边界递增排序, \(r\) 指针又可以直接向右移动并计算。
为实现上述操作,我们可以通过当前块编号的奇偶来决定排序顺序。
inline bool operator < (const Question& a, const Question& b) {//block[i]是第i个询问所属块的编号
return block[a.l]^block[b.l] ? a.l<b.l : (block[a.l]&1 ? a.r<b.r : a.r>b.r);
}//奇数块内右边界升序,偶数块内右边界降序
理论上该方法可以将效率提高一倍
2.曼哈顿距离最小生成树
设有 \(m\) 组询问。如果把每组询问 \((L_i,R_i)\) 看做平面上的一个点,如下分析莫队的时间复杂度:
已知询问区间 \((L,R)\) 求下一个询问区间 \((L',R')\) 需要的时间复杂度是 \(O(k\mid L-L'\mid+k\mid R-R'\mid)\) ,忽略常数可以看作是 \((L,R)\) 和 \((L',R')\) 两点间的曼哈顿距离,所以我们按照一定顺序计算全部询问可以获得的最小时间复杂度就是这全部 \(m\) 个点上的曼哈顿距离最小生成树的边长和。
但实际上求解曼哈顿距离最小生成树颇为繁琐,预处理量较大的同时也仅能带来常数级的优化,甚至负优化。除非遇到特殊的题目条件,我们一般就直接采用分块的方法对询问排序。
如果以后有更好的生成树写法再来填坑,我们先看看分块。
设块长为 \(len\) 。对于每一个询问, \(l\) 指针暴力挪动均摊是 \(O(len)\) 的,但一般而言并不会每个询问都把整个块长跑满,所以存在一个常数 \(S\in(0,1]\) 使得 \(l\) 指针的总时间复杂度是 \(O(S\cdot m\cdot len)\) 。对于每一个块里的询问, \(r\) 指针在整个序列中单向挪动 \(O(n)\) ,一共有 \(\frac{n}{len}\) 个块。所以最后双指针的总时间复杂度是 \(O(\frac{n^2}{len}+S\cdot m\cdot len)\) 。理论上,当 \(len=\frac{n}{\sqrt{S\cdot m}}\) 时,可以获得最小时间复杂度 \(O(n\sqrt{S\cdot m})\) 。当 \(n\) 、\(m\) 同阶时,忽略常数就得到经典的 \(O(n\sqrt n)\) 。
3.答案转移优化
已知询问区间 \((L,R)\) 我们将它转移到 \((L,R+1)\) (或其他3种),则需要加上原系列中 \(A_{R+1}\) (或其他3处对应的)对答案的贡献。一般地,转移该贡献是 \(O(1)\) 的,但有时可能会是更高级别的复杂度。此时我们可以考虑用其他数据结构维护此贡献来降低复杂度。总复杂度是 \(O(n\sqrt{m}\cdot f)\) ,其中 \(f\) 是每次转移贡献花费复杂度的均摊。
特殊地,在一些情况下我们可以不用每次及时计算转移的贡献。我们将每个转移操作看作一种另类的询问,记录下来(即将其离线),用另一个莫队来批量处理这些“询问”,称之为莫队二次离线。不严谨地说,对于一个形如 \(O(n\sqrt{n}\cdot f)\) 的复杂度,可以使用莫队二次离线优化为 \(O(n\sqrt{m}+n\cdot f)\) 。