《淺談亞 log 數據結構在 OI 中的應用》 - 學習筆記

向 $ 哥哥學習!

需要解決的問題:插入、刪除、前驅、後繼。不需要考慮相同元素。

2 壓位 trie

平衡樹和樹狀數組都沒什麼優化空間,把它們丟進垃圾堆裏。

考慮 trie 有沒有什麼操作。此時想起來 trie 似乎並不只能是二叉。

但是多叉有一個大問題:詢問的時候,如果子樹中沒有合法值,那就要在其他兒子裏找最大/最小值。也就是要在兒子集合裏尋找前驅後繼。

也就是說,對於一個 \(w\) 叉樹,需要支持大小爲 \(w\) 的集合的快速插入刪除前驅後繼。

比如 \(w=64\) ,那就可以通過二進制壓位來維護這個集合。前驅後繼都可以使用神奇的二進制操作 \(O(1)\) 實現。

於是複雜度 \(O(\log_w V)\) ,其中 \(V\) 是值域。

假裝值域不是太大,那麼各種操作可以自底向上實現,帶來各種剪枝,並且常數也比遞歸小很多。

當然如果值域太大那就只能動態開點了吧。

空間複雜度其實是 \(O(V/w)\) ,因爲大小不超過 \(w\) 的時候就只需要 \(O(1)\) 個整形了。但是如果動態開點就需要記錄兒子編號,就變回 \(O(V)\) 了。

拓展

插入、求 rank 。刪除可以當做是插到另外一棵樹裏。

此時的大問題是不能快速求前 \(k\) 個子樹的元素個數。

設叉數是 \(B\) 。如果每次插入都重新算一遍前綴和,那就 \(O(B\log_B V)\) 了,非常垃圾。

修改和詢問均衡一下,變成一個節點插入 \(B\) 次之後再重構,那麼插入的複雜度均攤下來就沒有問題。問題在於 \(B\) 個零散元素怎麼 \(O(1)\) 查詢。

仍然壓位。用 1 的個數表示某棵子樹內的零散元素個數,不同子樹之間用 0 隔開。那麼就只需要對一個二進制位查詢第 \(k\) 個 0 的位置。可以預處理。爲了讓預處理時間不爆炸只能 \(B<\log n\)

(我覺得)更簡單的做法:直接令 \(B=\sqrt{64}\) ,把 \(B^2\) 個位置均勻分給 \(B\) 個子樹,然後插入的時候直接填在對應位置即可。

3 vEB tree

壓位 trie 的瓶頸在於需要用二進制操作維護兒子集合,所以叉數不能太大。

但是發現對兒子集合的詢問也不外乎插入刪除、前驅後繼,這提示我們可以套娃。

對於一個大小爲 \(2^k\) 的 vEB tree ,令 \(m=k/2\) ,那麼就分出 \(2^{k-m}\) 個子樹。另外再開一個大小爲 \(2^m\) 的 vEB tree 維護所有兒子。

\(high(x),low(x)\) 表示 \(x\) 的高位低位;設 \(A_y\) 表示 \(y\) 對應的子樹;設 \(B\) 表示維護兒子的樹。

樹中維護 \(min,max\) ,表示集合的最大最小值。

較爲特殊的一點:我們不把 \(min\) 插入到子樹中,僅僅放在根節點的位置。這是爲了保證複雜度。

插入

如果 \(x<min\) 那麼 swap 一下。

如果對應兒子已經存在,那麼 \(B\) 中就不需要修改,直接給兒子插入即可。

否則新建對應兒子,然後把這個兒子插入到 \(B\) 裏。兒子裏只有一個元素所以不需要遞歸。

刪除

如果 \(x=min\) 那麼只需要更新新的 \(min\) 。在 \(B\) 和對應的 \(A\) 分別拿出 \(min\) 即可。然後變成在 \(B\) 和對應的 \(A\) 裏刪除新的 \(min\)

如果 \(x\) 對應的 \(A\) 大小爲 1 那麼直接刪掉,然後在 \(B\) 裏面刪除;否則 \(B\) 不需要修改,在 \(A\) 裏面刪除。

前驅後繼

如果 \(x\) 對應的 \(A\) 裏面有合法元素那麼直接遞歸。否則在 \(B\) 裏面找到前驅/後繼,然後直接把 min/max 拿走。

注意特判沒有插入的 \(min\)

簡單優化

當大小不超過 \(64\) 時直接用位運算幹掉。極大地減小遞歸深度。

空間複雜度

不太會分析也懶得分析,論文說是 \(O({2^k\over \sqrt w})\)

4 例題

第一題的壓位 trie 用法非常 trivial ,重點看第二題的樹上壓位 trie 合併。

【ZJOI 2019】語言

原做法是維護一些點的虛樹大小,要支持合併兩個子樹對應的虛樹。無腦做法就是線段樹合併。

我們考慮壓位 trie 能否支持合併。

因爲現在是動態開點壓位 trie ,所以要維護所有子節點的編號。合併的時候需要把其中一棵樹的兒子編號拉到另外一棵樹去,但是暴力拉就給複雜度乘了 \(w\) ,不太行。

一個兒子至少對應一個節點,所以用啓發式合併即可。

每拉一個子樹之後還要記得更新相鄰元素的距離和。

這時候又發現空間是 \(O(nw\log_w n)\) 有點爆炸。

重鏈剖分,每次直接把重兒子的 trie 拉過來。這樣任何時刻只有 \(O(\log n)\) 個壓位 trie ,每個 trie 只有 \(n/w\) 個點,空間複雜度變成 \(O(n\log n)\)

常數較小?不是很懂。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章