Dsu on tree 和 點分治
寫的有點亂,意識流 blog /yun
感謝 @Mr_Spade 對我的幫助。
從 Dsu on tree 到點分治
Dsu on Tree 的主要思想是輕重鏈剖分,使每個點作為根時,都繼承重兒子的資訊並快速加入根的影響,並對每個輕兒子暴力向重兒子合併。由於每個點被合併 \(O(\log n)\) 次,複雜度是正確的。
這種問題也可以用點分治實現。對於無根樹上的問題,對點分得到的若干部分分別遍歷,然後用和上面同樣的資料結構維護查詢和插入操作。
而對於有根樹中“對每個子樹求解”的問題則相對複雜。考慮有根點分治,記分治中心為 \(x\),\(x\) 下方的子樹為 \(x_1, x_2,\cdots, x_k\)
可以看到基本上 Dsu on tree 可以做的點分治也可以做,但實際上本質是差不多的。考慮你想要把 \(n\) 個一次多項式乘起來,你可以套用哈夫曼樹的方法,維護一個優先佇列,每次取兩個小的相乘然後放回堆中;或者使用分治,先將 \(f_l\times f_{l+1}\times \cdots \times f_{mid}\) 以及 \(f_{mid+1}\times f_{mid+2}\times \cdots \times f_r\) 算好,再計算 \([l,r]\) 全部相乘的結果。這分別對應著 Dsu on tree 和點分治,兩者都是將 \(n\) 個點合併,只是保證複雜度的方式不同。
一般而言做有根這種題都優選 Dsu on tree,因為效果幾乎一樣並且常數和程式碼都比較優秀。上面這段文字只是說明了:
Dsu on tree 能解決的問題為點分治的真子集
聽起來是暴論但實際上幾乎正確。考慮 Dsu on tree 的操作條件是:可以快速對重兒子加上根的影響;並且支援將輕兒子的內容高效動態插入。
然而一旦滿足這些條件,點分治也可以操作。但你發現在這方面的問題點分治的做法都比較繁瑣。這是因為點分治的擅長之處完全不在動態插入類問題。
點分治與靜態計算
考慮這樣一個問題:
給定兩棵樹 \(S,T\),帶邊權且 不一定非負。求 \(\max\limits_{u,v}\ \textit{dist}_S(u,v)+\textit{dist}_T(u,v)\)。
由於邊權非負,合併點集並由原來兩對最遠點得到新的最遠點的結論已不適用了。下面需要用到一個合併優化的技巧:
合併優化:對於當前分治中心連線的若干部分 \(x_1, x_2, \cdots, x_k\),每次選取大小最小的合併並放回作為一個新的部分。這樣可以證明效率能達到邊分治一樣的複雜度,具體證法可見 zzy 論文(?)。這個 trick 的功效在於,可以想邊分治一樣只用考慮兩個不同的部分而不是多個,可以為解題掃清一些障礙。
對於兩個在樹 \(S\) 上的點集 \(V_1,V_2\),我們建出 \(V_1\cup V_2\) 在 \(T\) 上的虛樹。首先求出 \(S\) 中每個點到分治中心的距離作為在 \(T\) 虛樹 \(T'\) 的點權。那麼只要得到 \(T'\) 上距離加上兩邊點權的最大值即可。具體實現類似於樹型 dp。可以發現這實際上實現了一個靜態問題的求解過程:建虛樹然後 dp。
那麼如果強行轉化為動態又將如何?設我們維護出 \(V\subseteq S\),然後一個新的點 \(x\) 需要被計算貢獻。那麼唯一的辦法是求出 \(T\) 上 \(V\) 中的點的點權加上與 \(x\) 距離的最大值,這本身就是極其複雜的問題。因此 Dsu on tree 直接怎麼做是不太行的。
可以發現,點分治擅長統計可以靜態計算的資訊。動態維護往往會比前者更加困難,或者是被轉化為一個“更強的問題”。那也不奇怪為什麼 Dsu on tree 的適用範圍更小了。
快速新增根的影響?
這是經常忽略的一個要點。考慮這樣一個題:
\(n\) 個點的樹,每個點 \(i\) 有一次多項式 \(x-a_i\)。求每個點到根結點 \(1\) 路徑上多項式的乘積,輸出它們求和後的結果。
這題有一個基於長鏈剖分鏈分治的做法,這裡略去。
暴力做法是對子結點求和,並乘上自己,複雜度平方。考慮有根點分治(一下記號和上文一致):對於分治中心 \(x\),我們先遞迴計算 \(x_1, x_2, \cdots, x_k\),對它們的多項式求和記作 \(g\),這裡複雜度不超過 \(x\) 子樹大小。然後遞迴 \(x\) 父親方向的連通塊,那麼這時除了子樹 \(x\) 都完成對 \(r\) 貢獻的統計了。
唯一的問題是 \(x\to r\) 的多項式 \(f\) 怎麼算。比較無腦的有分治 FFT,這樣總複雜度 \(O(n\log ^3 n)\),可以利用之前遞迴子問題結果的技巧將其去掉一個 \(\log\),這個暫且不談。
嘗試使用 Dsu on tree?對於重兒子求出答案,乘上根直接作為根的答案,然後對所有輕兒子合併?不幸的是“乘上根”這個操作複雜度就和重子樹相關了——多項式乘法沒法像資料結構那樣快速新增影響。實際上稍微冷靜一下發現這玩意就是暴力。
更多的時候,這一點可能表現為”難以全域性打 tag“之類的情況。
本質原因……
具體的,dsu on tree 的短板歸納為兩點:
- 在靜態計算簡單但維護動態新增困難的時候,無法快速合併輕子樹;
- 無法高效加入根的影響;
可以發現上面兩點說明了一件事:dsu on tree 高度依賴動態新增的高效方法。
為什麼會這樣?注意到點分治之所以可以隨意遍歷整個連通塊,是因為重心分解的分治結構做了保障。而 Dsu on tree 卻必須靠規避重子樹來保證複雜度。
例子
給定一顆樹,帶邊權,求路徑上邊權平均值 \(\in [L,R]\) 的路徑數。
首先記 \((c,s)\) 為一條邊數為 \(c\) 總權值為 \(s\) 的路徑。那麼對於兩條路徑 \((c_1,s_1),(c_2,s_2)\),由合法條件可以推出兩個不等式:
\[s_1-c_1R\le -(s_2-c_2R) \\ s_1-c_1L\ge -(s_2-c_2L) \\ \]這個式子一邊都只與一條路徑有關,抽象成兩維座標就是數點問題了。考慮點分治做法,如果是採用合併優化並靜態統計的方法其實很容易,只是簡單的掃描線和資料結構。這樣總複雜度為 \(O(n\log^2 n)\)。
而另一種動態新增的思路就比較困難——天然的強制線上使得問題棘手了很多,我們似乎避不開高階資料結構,甚至可能還有更劣的複雜度。
本文來自部落格園,作者:-Wallace-,轉載請註明原文連結:https://www.cnblogs.com/-Wallace-/p/dsu-on-tree-and-vdac.html