1. 程式人生 > 其它 >[題目小結] 可持久化資料結構

[題目小結] 可持久化資料結構

呱呱呱! 目錄

\(\text{[ONTAK 2010] Peaks}\)

解法

首先可以想到是 \(\rm Kruskal\) 重構樹,問題轉化為求重構樹中某子樹的第 \(k\) 大權值。

先開始想寫線段樹合併,後來發現要寫可持久化,不然由於中間的版本將地址給了後面的版本,就不能查詢中間版本了。

其實這題也可以用 \(\rm dfs\)

序解決,將子樹轉化成序列問題。對於本來就有的點,加入自己;對於每個重構樹新增的點,記錄 \(st,ed\),最後查詢這個區間中的答案即可。

程式碼

\(\text{Link.}\)

訓練士兵

解法

將詢問和修改差分。令修改 \((x,y,w)\) 表示將橫縱座標都分別大於 \(x,y\) 的點都加上 \(w\)。這樣我們需要考慮 \((x,y,w)\)\([1,1]-[i,j]\) 的貢獻

\[\text{Ans}=\sum_{x=1}^i\sum_{y=1}^j (i-x+1)\cdot (j-y+1)\cdot w_{x,y} \]\[=(i+1)(j+1)\cdot \sum_{x=1}^i\sum_{y=1}^j w_{x,y}-(j+1)\cdot \sum_{x=1}^i\sum_{y=1}^j x\cdot w_{x,y}-(i+1)\cdot \sum_{x=1}^i\sum_{y=1}^j y\cdot w_{x,y}+\sum_{x=1}^i\sum_{y=1}^j xy\cdot w_{x,y} \]

所以只需要維護四個變數即可。

由於是先修改再查詢,我們可以不使用樹套樹,而是將 \(x,y\) 座標離散化後,以 \(x\) 座標為主席樹的根,將 \(y\) 在主席樹上維護。查詢也是 \(\mathcal O(q\log n)\) 的。

程式碼

\(\text{Link.}\)

\(\text{Couple Trees}\)

題目描述

給定兩棵大小均為 \(n\) 的樹,它們共用相同的節點。保證 父親的編號一定比兒子小

\(m\) 個詢問,每個詢問為 \((x,y)\),詢問從樹 \(1\)\(x\),樹 \(2\)\(y\) 開始向上走,能走到相同 \(\rm id\) 的最深的點。你需要輸出這個點以及 \(x,y\)

到這個點的步數。

解法

可以先將樹 \(1\) 來一個樹剖,這樣 \(x\) 到根的路徑可以被表示成幾段區間。對樹 \(2\) 做主席樹,兒子繼承父親的版本,在樹 \(1\)\(\text{dfn}_x\) 的地方插入 \(x\)。這樣對於查詢,我們可以在第 \(y\) 個版本的主席樹中查詢 \(x\) 到根路徑被表示的區間,這是 \(\mathcal O(q\log^2 n)\) 的。

\(\text{JZOJ - 4616}\) 二進位制的世界

題目描述

給定長為 \(n\) 的序列 \(a\),你需要對於每個 \(i\in[2,n]\),求出 \(a_i\text{ opt }a_j\) 的最大值,其中 \(j\in[1,i)\)\(\text{opt}\in \{\text{or,and,xor}\}\)

\(n\le 10^5,0\le a_i<65536\)

解法

最樸素的暴力是記錄數 \(x\) 是否出現過,\(\mathcal O(1)\) 插入,\(\mathcal O(2^{16})\) 詢問。

我們可以增大插入時間,減少詢問時間。令 \(f_{i,j}\) 為前 \(8\) 位為 \(i\),後 \(8\) 位為 \(j\) 的數進行運算 \(8\) 的最大值(這裡其實前/後沒啥區別)。那麼假設插入的數前後 \(8\) 位分別為 \(x,y\),就有

\[f_{x,j}=\max\{f_{x,j},j\text{ opt }y\} \]

假設詢問的數字前後 \(8\) 位分別為 \(x,y\),就有

\[\text{Ans}=\max\big \{f_{i,y}+(i\text{ opt }x)\cdot 2^8\big \} \]

\(\text{K }\)個串

解法

題目是比較經典的堆的問題,因為 \(k\le 2\cdot 10^5\),我們可以以 \(i\in[1,n]\) 作為右端點,先將右端點為 \(i\) 的最大子串塞進堆,取出堆頂,發現最大子串將左端點區間分成兩段,在兩段中各取最大值即可。

問題是如何快速維護右端點為 \(i\),左端點在某一區間的最大子串。可以考慮以右端點為根建立主席樹,左端點對應著樹內下標。這樣每次右移右端點 \(i\),它的貢獻就是在 \((lst_{a_i},i]\) 中加上 \(a_i\)

實現上為了控制記憶體,寫了個標記永久化(這樣每次還是增加大概 \(\log n\) 個點),記錄一下幾個小 \(\rm bug\)

  • ins() 函式中,我們不能將 \(\rm lazy\) 標記寫成函式的遞迴傳遞,每次加上這個節點的 \(\rm lazy\)。因為在修改的時候,這個節點 \(o\) 的兒子可能不被修改覆蓋,這個兒子就沒有加上 \(\rm lazy\) 標記,更新 \(o\) 就會出錯。最好的寫法是修改時不將標記下傳,在 pushUp() 時加上標記即可。這樣後面 ask() 也更好辦一點。
  • 每次修改區間之後,不能直接將 \(rt_i\) 的最大值塞進堆。這也是寫法的問題,因為 \(rt_i\)\([1,n]\) 的最大值,而 \([i+1,n]\) 初始是 \(0\),如果 \([1,i]\) 區間內的數都 \(<0\) 就會出錯。

程式碼

\(\text{Link.}\)

\(\text{BZOJ - 4771 }\)七彩樹

解法

樹套樹?似乎也不好處理顏色。題解的做法是以深度建立主席樹,維護樹上 \(\text{dfs}\) 序。插入時將節點按深度排序。假設處理到 \(x\),那麼 \(x\) 對到根的一條鏈都貢獻 \(1\),找到與其同色且先插入的點中的前驅 \(pre\) 與後繼 \(nxt\),那麼 \(\text{lca}(pre,x),\text{lca}(nst,x)\) 的貢獻就 \(-1\) 了,而且 \(\text{lca}(pre,nxt)\) 還要 \(+1\)。另外都已經在插入 \(pre,nxt\) 時被處理過了。

複雜度 \(\mathcal O(n\log n)\)

程式碼

\(\text{Link.}\)