本文轉自http://fanhq666.blog.163.com/blog/static/81943426201172472943638/
求樹重心的方法:(NlogN)
http://www.cnblogs.com/qlky/p/5780933.html
還記得曾經提到過的樹的“重心”嗎?重心的定義是:以這個點為根,那么所有的子樹(不算整個樹自身)的大小都不超過整個樹大小的一半。
樹的重心的一個的性質:
樹中所有點到某個點的距離和中,到重心的距離和是最小的;如果有兩個重心,那么他們的距離和一樣。
這也是“道路修建”帶來的啟發。(證明:調整法)
樹的重心的另一個性質:
把兩個樹通過一條邊相連得到一個新的樹,那么新的樹的重心在連接原來兩個樹的重心的路徑上。
這個讓“重心”名副其實了。。。(證明:。。。自己好好思考一下吧。。。)
還有一個性質:
把一個樹添加或刪除一個葉子,那么它的重心最多只移動一條邊的距離。
(證明:提示:張放想都沒想說,要不然那兩個不等式就矛盾了)
嗯,不錯,有這么多性質,可以出不少惡心題了。。。
不過,我還是更關心一個事情:重心的動態維護。
如何動態呢?
情景1:添加一片葉子
根據已有性質,添加一片葉子之后新的重心要么不動要么向那片葉子的方向移動一下。這樣,可以用一個link-cut tree來維護。
我們以重心為根建立這個動態樹。每個節點維護它所在的子樹的大小。添加葉子等於向一條路徑上的維護值增加1,這個可以通過打標記實現。發現不得不移動的時候進行一次換根的操作。因為只可能移動1,所以換根的操作是可以完成的。
我們甚至還可以維護所有點到重心的距離和!這個只需給每個點加一個維護值:這個點為根的子樹中所有點到這個點的距離和,通過稍微有點復雜的標記系統還是可以維護的。
情景2:刪除一片葉子
只有刪除操作?那么離線改成添加吧。。。
不允許離線?那么我們要換一個思路:
定義稍微廣義的樹的重心:每個點有一個非負權,每個邊有個正的長度,到所有點的權乘以距離的和最小的點定義為重心。
注意:樹的重心的位置和邊的長度沒有關系!。
在只有權值修改的情況下,我們可以利用樹分治配合基本數據結構來維護樹的重心:
注意到,我們可以維護一個子樹內的點的權值和(利用dfs序)。這樣給定一條邊,我們就能夠知道樹的重心在這條邊的哪邊(看看哪邊權值和大就行了)。
這樣,我們可以先找一個比較靠近中心的邊,問問應該向哪邊走,再分治下去,就像樹分治那樣(類似二分查找?)。
當然,要想一下其他的技巧來對付”星型數據“,這個應該不難(通過拆點、拆邊的技巧)。
利用這個廣義一點的重心,我們發現,刪除操作其實就是把權修改成0而已,可以在log^2N的時間內動態維護了。
如何處理多重心的情況?”抖動“一下權值使得只有一個重心不就行了。。。那另一個重心在哪里?這個只是個細節問題。。。
能否維護距離和?能否在logN的時間內維護?歡迎討論(將子樹和查詢與樹分治結合起來?。。。)。
情景3:移動一片葉子
把一個葉子移動到另一個地方。
這個怎么維護呢?其實,我們發現,新的重心應該在原來的重心和葉子新的位置的連線上(證明?應該是對的吧),移動距離很小。於是,也就可以維護了。
情景4:移動一個子樹(被移動的子樹小於原樹的一半,並且保持它的根不變)
這個可以維護嗎?
新的重心在原來重心和新子樹的接合點的連線上嗎?(證出來的歡迎留言)
有一個可以和link-cut tree配合使用的工具,叫做Euler-tour tree,它維護樹的歐拉回路,基本元素是邊。利用它,可以方便的完成子樹的移動,並且給定一條有向邊,可以回答這條邊指向的子樹的大小(Eurler-tour tree中沒有”根“以及”父親“這個概念!)。如果上面的那個論斷是對的,那么這個應該可以維護了(復雜度?log^2N吧。。。)
另一個思路是樹塊划分,把樹划分成若干聯通塊,並且在查詢的時候合並相鄰的聯通塊使得每個聯通塊的大小都是sqrt(N)的級別。這個東西對維護是否有幫助?歡迎交流。。。
情景5:開始N個一個點的樹,每次用一條邊合並兩個樹,要求回答新的樹的重心
離線?在線?logN?log^2N?sqrt(N)?
等待你去探索
情景6:未來的某一天,某省省選出了一個叫做”瘋狂的重心“的數據結構題,時限1分鍾,內存若干G,標程幾十K,當場無人做。。。。。。