背景
你 Ki 叔 最近 CF 虐場的同時發明了一種趣味的東西,適用於區間修改查詢問題,但合並兩個區間的貢獻復雜度需要與區間長度有關的問題,這種問題無法用普通線段樹去維護,因為復雜度爆炸,過去一般會使用分塊維護,需要討論散塊、整塊等問題,較為復雜,而神仙大 Ki 子研究出了一種新的方式,代碼好寫,想法基於在線段樹上對節點大小進行根號分治。
理論
理論具體來說:
- 設立一個閾值 \(B\)
- 修改的時候,對於線段樹節點大小 \(\le B\) 的節點,暴力 pushup,\(> B\) 的時候則不管。
- 查詢的時候,只有當前節點完全屬於查詢區間且節點大小 \(\le B\) 時,在該點上相應查詢。
寫起來很好寫,就 pushup 和 query 的時候加一個 if 條件就行。
事實上,相當於是舍棄了線段樹上方一部分的結構,也可以理解維護了 \(\frac{n}{B}\) 級別顆線段樹。
這樣做復雜度是啥子?設 pushup 一個長度為 \(a\) 的區間的復雜度是 \(O(a \times X)\),查詢一個線段樹節點的答案復雜度是 \(O(Y)\)。
- 考慮 Pushup。根據線段樹理論每層只會遞歸到兩個節點,因此區間總長度是 \(B + \frac{B}{2} + \frac{B}{4} + \dots = 2B\) 級別的,復雜度是 \(O(B \times X)\)
- 考慮 Query,定義最高層是節點大小 \(\le B\) 最大的那一層,遞歸到最高層下面的最多兩個區間,復雜度應當是 \(O((\frac{n}{B} + \log B) \times Y)\),通常 \(\log\) 要小,可以視為 \(O(\frac{n}{B} \times Y)\)
這個均攤大概是 \(B = \sqrt \frac{nY}{X}\),總復雜度 \(O(q\sqrt{nXY})\)。
例 1:CF1540D. Inverse Inversions
經過若干步轉化,問題變為維護一個序列 \(b\),若干次查詢:
-
\(b\) 單點改
-
給一個值 \(v\),以及 \(p\),執行:
for i = p; i <= n; i++: if v >= b[i]: v++
輸出最后的 \(v\)
考慮進行一段區間的這樣操作,設 \(f(x)\) 為把 \(x\) 丟出去出來的會是啥,這個函數是連續不降的,函數不同的值只有區間長度種,可以分段維護函數,考慮合並的時候可以用類歸並的方式 \(O(長度)\) 合並。
查詢的時候直接按順序在維護分段函數上二分 / lower_bound 就行。
這樣復雜度是 \(O(q\sqrt{n \log n})\)。
例 2:CF1129D Isolation / [BJOI2017]開車
兩題最后可以轉化為這樣一個數據結構問題:
有 \(n\) 個東西排成一排 ,有兩個屬性 \(a_i, b_i\),每次:
- 給 \([l, r]\) 區間的 \(a\) 加減一個權值 \(w\)
- 詢問一個區間 \([l, r]\) 中,所有 \(a\) 在區間 \([x, y]\) 的 \(b\) 的和。
對於一個區間,可以維護按 \(a\) 排序的結果並且預處理 \(b\) 前綴和,查詢就可以在上面二分做到 \(\log\),而排序可以從下面兩個兒子線性歸並排序。
復雜度是 \(O(q\sqrt{n\log n})\) 的。
這比傳統的分塊,把 \(\log\) 放根號里了,但事實上原本也可以做到,只不過仍然需要在散塊中歸並排序,其實與 ki 子線段樹原理本質是一樣的,但這個統一且和諧,簡單很多!
這兩題確實也有沒有 \(\log\) 做法,是開一個桶,但依賴於相鄰兩個值差不超過 \(1\) 這種限制。