contents
Problem
題目有三種字符串的操作:
- 1 p s: 在當前字串位置 p 後插入 s 字串。
- 2 p c: 將當前字串位置 p 後面連續 c 個字符移除。
- 3 v p c: 在版本號 v 的字串中,在位置 p 之後印出 c 個字元。
由於怕離線處理,因此輸入的數值會進行加密,加密的原則-每個數字會增加數值 d,其 d 為當前打印字符 c
的個數。
Sample Input
|
|
Sample Output
|
|
Solution
代碼算法詳見 《可持久化數據結構研究》杭州外國語學校-陳立杰 那篇所寫的 Treap(樹堆)。
這題的離線作法是預先紀錄需要的版本號位置,在邊處理的時候隨時保存答案,以免造成記憶體過大儲存。既然他進行了加密,可見我們不能把每一個版本號的字串儲存,這就是其麻煩之處。
接下來說明的作法,是將每個字符當作節點,在一個二元搜尋樹上,利用中序走訪的結果表示一個字串。
可持久化 Treap
Treap 主要概念是平衡樹,也就是儲存有序的結構,支持插入、刪除、查找數字。冠上可持久化平衡樹,就是一個維護歷史版本的平衡樹。
Treap 每一個節點具有 <key, value>
,仍然是一個二元搜尋樹,但同時也是一個 heap。單純看 key 呈現一個二元搜尋樹,如果看 value 則會是 heap。key 跟 value 是挺像的,我的代碼誤打了名稱。而 Treap 效率完全取決於隨機決定的 value 值進行平衡調整。
對於可持久化 Treap 而言,是一個具有平衡樹特性,但不需要做任何旋轉操作的樹。每一次將修改操作替代為一個增加節點的方式 ( 用增加替代修改 的精神),在記憶體耗量就是 O(m log n)
,m 是其操作,log n 是樹內結構的期望深度。每一次操作期望只修改 log n 個節點。
操作定義
這裡只會講到可持久化,拆分成兩個操作,而原本的插入、刪除、查找都可以利用這兩個操作完成。
treap merge(treap a, treap b) <treap, treap> split(treap a, int n)- merge a b : 將兩個 Treap a, b 進行合併成一個 Treap。
- split a n : 將 Treap a 拆成前 n 小個元素為一個 Treap,剩餘為另一個 Treap。
插入一個元素相當於 split 一次,將兩個部分中間放置一個元素後,依序將其 merge 起來。
刪除一個元素相當於 split 兩次,將中間單一元素的 Treap 忽略,將第一和第三部分的 Treap merge 起來。
merge()
treap merge(treap a, treap b)- 若 a, b 其中一個是 null treap,回傳另一個非空樹。
- 若key(a)≤key(b)
讓 a 的左子樹不變,而 a 的右子樹變成merge(right(a),b) 的 treap 結果。 - 若key(a)>key(b)
讓 b 的右子樹不變,而 b 的左子樹變成merge(a,left(b)) 的 treap 結果。
split()
<treap, treap> split(treap a, int n)- 若size(left(a))≤n{l,r}=split(left(a),n) ,並且將 a 的左子樹改成 r。返回結果{l,a}
- 若size(left(a))>n{l,r}=split(right(a),n−size(left(a))−1) ,並且將 a 的右子樹改成 l。返回結果{a,r}
另解
在 g++ 头文件中,
<ext/rope>
中有成型的块状链表,在using namespace __gnu_
cxx; 空间中,其操作十分方便。
不用手刻,而且歷史版本的儲存速度也是 O(1)
,裡面應該進行了很多記憶體控管。沒有細讀過,但是其支持相當厲害。看到這一篇代碼被不到百行的流程搞定。不過塊狀鏈表的複雜度仍然是O(n1.5),比起隨機的可持久化 treap 還是慢了些。
|
|