首先不能看錯題。每次的答案是所有隊列的並的不同元素個數。
可以想到對於每個操作,求出它塞進去的元素消失的最晚時間,然後就可以把相同元素的操作放在一起。於是現在權值已經不重要了,我們只關心它這次塞進去的元素什麼時候消失。
然後有整體二分或莫隊的思路,或多或少都需要把序列分成若干塊來保證複雜度,但是不確定能不能優化到 \(O(n\sqrt n)\) ,所以就不看了。
直接對序列分塊。考慮求出一個操作在每個位置的消失時間。
先考慮它在一個零散位置什麼時候消失。一個經典的想法是把“區間插入”差分,然後從左到右推每個位置的插入序列。那麼在這個位置的消失時間就可以通過線段樹二分在 \(O(\log n)\) 的時間內求出。共有 \(O(B)\) 個零散位置,所以複雜度 \(O(nB\log n)\) 。
然後是整塊。對於每個整塊,把操作序列拎出來跑 two pointers ,用線段樹維護每個位置剩的空間,即可得到每個覆蓋該塊的操作的存活時間。複雜度 \(O({1\over B}n^2\log n)\) 。
於是獲得了 \(O(n\sqrt {n}\log n)\) 垃圾做法。
優化整塊
注意到一個操作只會有兩個零散塊的貢獻,所以把所有整塊的零散操作個數加起來僅僅是 \(O(n)\) 的。
所以對一個整塊做 two pointers 時只需要暴力做零散操作的區間加減,用一個 tag 維護整塊加減即可。總複雜度 \(O(n^2/B+nB)\) ,分別是所有操作覆蓋的整塊數和暴力給每個操作的零散塊做區間加減的複雜度。
這時候已經可以衝過所有數據了。
優化零散塊
仍然考慮一次做一整個塊。在這一整個塊裏,某一些時間是確定會有一個插入操作的(即覆蓋了這個塊的操作),而剩下的時間就只有一個區間有插入操作。
處理出每個時間前綴的確定操作的個數(就是一個前綴和)和第 \(i\) 個確定操作的時間,然後對於每個位置,把零散操作拿出來跑 two pointers 即可。
總複雜度 \(O(n\sqrt n)\) ,非常牛逼。