莫隊算法優化
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)\) 。