數列分塊九講
本部落格是數列分塊入門 1~9 的題解,博主將會對九道題一一講解,先給出連結:
接下來進行講解,包括思路和程式碼,且如果博主有想到非分塊寫法會一併給出,但沒有程式碼。
約定序列長度為 \(n\) ,詢問次數 \(m\) 與 \(n\) 同階,值域大小為 \(|V|\)。
#6277. 數列分塊入門 1
分塊入門中的入門,要求支援區間加,單點查。
非分塊做法顯然,直接上差分樹狀陣列就能解決,程式碼也很短,但既然是分塊專項,還是試著分塊解決一下。
分塊這一資料結構的時間複雜度常常與根號掛鉤,而和根號掛鉤的演算法常常有一個特點——優雅的暴力。
分塊也脫不了關係,分塊的思想常常是很暴力的,本題也是如此,我們可以將整個序列分成許多塊,塊長為 \(T\),則塊數為 \(\dfrac{n}{T}\),對於整塊,每次區間修改,我們可以考慮對於整塊打一個與線段樹的 \(\texttt{lazytag}\) 相似的標記,表示整個區間的變化量,這樣的話我們對於修改區間中的整塊,就可以做到 \(\mathcal O\left(1\right)\) 修改,對於區間兩端不完整的塊,最多隻有兩個,可以直接暴力修改,這樣的話整塊最多有 \(\mathcal O\left(\dfrac{n}{T}\right)\)
預處理就是把每個元素所屬的塊、塊的左右端點標號,時間複雜度 \(\mathcal O\left(n\right)\)
查詢直接自己的值加上區間的加標記就好了,時間複雜度 \(\mathcal O\left(1\right)\)
總時間複雜度 \(\mathcal O\left(n\sqrt n\right)\)。
程式碼
#6278. 數列分塊入門 2
本質上和上一題一樣,但是查詢變了,變成了查詢比區間一個數小的數的個數,即查詢區間排名。
非分塊解法似乎可能能用線段樹套可持久化 \(\texttt{FHQ-Treap}\) 之類的,也可能不行,博主太菜了不懂,但是分塊解法非常簡單。
老套路,先分成 \(T\) 大小的 \(\dfrac{n}{T}\) 塊(下文中默認同上,塊長 \(T\),塊數 \(\dfrac{n}{T}\)),再考慮整塊和散塊的修改和查詢。
為了方便查詢塊內排名,可以想到在塊內執行排序,這樣就可以更快地塊內查詢。
發現這塊打加法標記不影響塊內排序,所以整塊修改還是 \(\mathcal O\left(1\right)\) 的,而散塊修改過一些以後,塊內只有部分元素被修改了,所以需要重新排序,散塊修改的時間複雜度是 \(\mathcal O\left(T\log T\right)\) ,總時間複雜度 \(\mathcal O\left(\dfrac{n}{T}+T\log T\right)\)。
查詢的時間複雜度,散塊直接數,時間複雜度 \(\mathcal O\left(T\right)\) ,整塊裡面二分查詢,查詢總時間複雜度 \(\mathcal O\left(\dfrac{n}{T}\log T\right)\)。
預處理比上面多了個預處理塊內排序,時間複雜度 \(\mathcal O\left(n\log T\right)\)。
這樣看來總時間複雜度是 \(\mathcal O\left(n\left(T\log T+\dfrac{n}{T}\log T\right)\right)\)。
還是當 \(T=\sqrt n\) 時時間複雜度最小取 \(\mathcal O\left(n\sqrt n\log\sqrt n\right)\)。
程式碼
#6279. 數列分塊入門 3
和上題一模一樣,求前驅和求區間排名都是有序陣列 \(\mathcal O\left(\log n\right)\) 做的事,直接和上題一樣的做法,將查詢做一點點修改就好了。
順便提一嘴,這道題好像資料有鍋,具體討論區裡有,大概就是體面裡說會有負數,但實際上資料裡面沒有,就當作資料裡沒有負數做就好了。
程式碼
#6280. 數列分塊入門 4
區間加區間和的經典題。
非分塊演算法顯然,樹狀陣列、線段樹都可以做。
分塊做法其實和第一題一模一樣,無非再記一個區間和陣列就完了,時間複雜度上查詢變成和修改一樣,別的不變,總時間複雜度還是 \(\mathcal O\left(n\sqrt n\right)\)。
程式碼
#6281. 數列分塊入門 5
前面都是數列分塊模板題中的模板題,這道題開始需要一些思考了。
區間開方區間和,結論題。
首先講結論,即一個數開方開不了幾次就變成 \(1\) 了,\(x\) 的開方次數級別是 \(\mathcal O\left(\log\log x\right)\),證明如下:
設一個數為 \(v\),首先顯然如果 \(2^k\ge v\),那麼 \(\left\lfloor\sqrt[k]v\right\rfloor=1\),找到最小的 \(k=\left\lceil\log v\right\rceil\),然後每次平方等價於 \(k\gets2k\),這樣的話平方操作的次數就是 \(\log k\)。
綜上所述,開方次數約是 \(\mathcal O\left(\log\log|V|\right)\)。
得到了這個結論就可以做這道題了,查詢操作同上一題不解釋,修改操作散塊依然直接做,整塊記一個塊內最大值,如果最大值為 \(1\) 顯然這個區間可以跳過,因為每個數最多做 \(\mathcal O\left(\log\log v\right)\) 次,所以每個塊最多做 \(\mathcal O\left(T\log\log|V|\right)\) 次就不用再做了,每次做完暴力重構這個塊,修改的均攤總時間複雜度是 \(\mathcal O\left(n\log\log v\right)\),但是因為即使全都為 \(1\) 每個區間你還是要都判斷過去,所以除了開根號的操作次數以外還要有一個判斷的時間複雜度,即每次修改操作還額外多一個 \(\mathcal O\left(\dfrac{n}{T}+T\right)\)。
總時間複雜度 \(\mathcal O\left(n\log\log|V|+n\left(\dfrac{n}{T}+T\right)\right)\),同樣當 \(T=\sqrt n\) 時時間複雜度達到最優的 \(\mathcal O\left(n\log\log|V|+n\sqrt n\right)\)
程式碼
#6282. 數列分塊入門 6
任意位置插入一個數、查詢第 \(k\) 個數。
非分塊做法一眼就是平衡樹模板題,隨便一棵都行,實際上這題資料過於隨機 vector
直接模擬就過了。
畢竟還是分塊練習,考慮分塊做法。
說說考慮一下,其實也就是把原數列分成幾塊,最開始每塊裡單次插入的時間複雜度是 \(\mathcal O\left(T\right)\) 的,但是如果故意來卡每次操作到同一個塊內最後時間複雜度還是會退化成 \(\mathcal O\left(n\right)\) 的,但本題資料隨機就過去了。
查詢更簡單,先整塊整塊跳,當剩餘排名比塊內少時在塊內找,時間複雜度 \(\mathcal O\left(\dfrac{n}{T}+T'\right)\),這裡的 \(T'\) 表示修改到現在為止最大的塊長,最大會變成 \(\mathcal O\left(n\right)\)。
考慮如果不隨機該怎樣保證時間複雜度。
發現每次將序列重新分塊時間複雜度是 \(\mathcal O\left(n\right)\) 的,所以如果重新分塊次數不多,時間複雜度可以接受。
設初始塊長 \(T=\sqrt n\),此時修改與查詢時間複雜度都是 \(\mathcal O\left(\sqrt n\right)\)。
考慮在多次修改後怎麼保證時間複雜度。