支配树学习笔记
@(學習筆記)[支配樹]
簡介
什么是支配樹?支配樹是什么?XD
對于一張有向圖(可以有環)我們規定一個起點\(r\), 從\(r\)點到圖上另一個點w可能存在很多條路徑(下面將r到w簡寫為\(r\to w\)).
如果對于\(r\to w\)的任意一條路徑中都存在一個點\(p\), 那么我們稱點\(p\)為\(w\)的支配點(也可以稱作是\(r\to w\)的必經點), 注意\(r\)點不討論支配點. 下面用idom[u]表示離點u最近的支配點.
對于原圖上除\(r\)外每一個點\(u\), 從\(idom[u]\)向\(u\)建一條邊, 最后我們可以得到一個以\(r\)為根的樹. 這個樹我們就叫它"支配樹".
聯想
這個東西看上去有點眼熟?
支配點和割點(刪掉后圖聯通塊數增加)有什么區別?
我們考慮問題給定一個起點\(r\)和一個終點\(t\), 詢問刪掉哪個點能夠使\(r\)無法到達t.
很顯然, 我們刪掉任意一個\(r \to t\)的必經點就能使\(r\)無法到達\(t\), 刪掉任意一個非必經點, \(r\)仍可到達\(t\).
從支配樹的角度來說, 我們只需刪掉支配樹上\(r\)到\(t\)路徑上的任意一點即可
從割點的角度來說, 我們是不是只需要考慮所有割點, 判斷哪些割點在\(r\to t\)的路徑上即可?是否將某個割點刪掉即可讓\(r\)無法到達\(t\)?
這當然是不正確的, 我們可以從兩個方面來說明它的錯誤:
?
這個圖中點\(u\)是關鍵點(刪掉后圖聯通塊個數增加)
并且\(u\)在\(r\to t\)的路徑上, 然而刪掉點\(u\)后\(r\)仍然可以到達\(t\)
?
在這個圖中不存在任何割點
所以我們沒有辦法使用割點來解決這個問題.
簡化問題
對于一棵樹, 我們用r表示根節點, u表示樹上的某個非根節點. 很容易發現從\(r\to u\)路徑上的所有點都是支配點, 而\(idom[u]\)就是\(u\)的父節點.
這個可以在\(O(n)\)的時間內實現.
因為是有向無環圖, 所以我們可以按照拓撲序構建支配樹.
假設當前我們構造到拓撲序中第\(x\)個節點編號為\(u\), 那么拓撲序中第\(1\) ~ \(X - 1\)個節點已經處理好了, 考慮所有能夠直接到達點\(u\)的節點, 對于這些節點我們求出它們在支配樹上的最近公共祖先\(v\), 這個點\(v\)就是點\(u\)在支配樹上的父親.
如果使用倍增求LCA, 這個問題可以在\(O((n+m)\log n)\)的時間內實現.
對于這兩個問題我們能夠很簡便的求出支配樹.
有向圖
對于一個有向圖, 我們應該怎么辦呢?
簡單方法:
我們可以考慮每次刪掉一個點, 判斷哪些點無法從r到達.
假設刪掉點\(u\)后點\(v\)無法到達, 那么點u就是\(r\to v\)的必經點(點\(u\)就是\(v\)的支配點).
這個方法我們可以非常簡單的在\(O(nm)\)的時間內實現.
其中\(n\)是點數, \(m\)是點數.
更快的方法:
這里, 我將介紹Lengauer-Tarjan算法.
這個算法能在很短的時間內求出支配樹.
關于這個算法的具體證明, 推薦一下兩篇博客:
http://www.cnblogs.com/meowww/p/6475952.html
https://ssplaysecond.blogspot.jp/2017/03/blog-post_19.html
本篇博客主要講解的是Lenguaer-Tarjan算法的實現.
首先來介紹一些這個算法的大概步驟:
點\(x\)在DFS序中的位置.
半必經點
我們用idom[x]表示點x的最近支配點, 用semi[x]表示點x的半必經點.
那什么是半必經點呢?
對于一個節點\(Y\), 存在某個點\(X\)能夠通過一系列點\(p_i\)(不包含\(X\)和\(Y\))到達點\(Y\)且\(\forall p_i\)都有\(dfn[p_i]>dfn[Y]\), 我們就稱\(X\)是\(Y\)的半必經點, 記做\(semi[Y]=X\)
當然一個點\(X\)的"半必經點"會有多個, 而且這些半必經點一定是搜索樹中點\(X\)的祖先(具體原因這里不做詳細解釋, 請自行思考).
對于每個點, 我們只需要保存其半必經點中dfn最小的一個, 下文中用semi[x]表示點x的半必經點中dfn值最小的點的編號.
我們可以更書面一點的描述這個定理:
?
?
在這些必經點中, 我們僅需要dfn值最小的
這個半必經點有什么意義呢?
我們求出深搜樹后, 考慮原圖中所有非樹邊(即不在樹上的邊), 我們將這些邊刪掉, 加入一些新的邊 \(\{ (semi[w],w) : w∈V 且 w \ne r \}\), 我們會發現構建出的新圖中每一個點的支配點是不變的, 通過這樣的改造我們使得原圖變成了DAG
是否接下來使用DAG的做法來處理就可以做到\(O((n + m) \log n)\)呢?我沒試過, 不過還有更好的方法.
必經點
一個點的半必經點有可能是一個點的支配點, 也有可能不是. 我們需要使用必經點定理對這個半必經點進行修正, 最后得到支配點.
對于一個點\(X\), 我們考慮搜索樹上\(semi[X]\)到\(X\)路徑上的所有點\(p_0, p_1 ... p_k\). 對于所有\(p_i: 0 < i < k\), 我們找出\(dfn[semi[p_i]]\)最小的一個\(p_i\)記為\(Z\)
考慮搜索樹上\(X\)與\(semi[X]\)之間的其他節點(即不包含\(X\)和\(semi[X]\)), 其中半必經點dfn值最小的記為\(Z\)
如果\(semi[Z]=semi[X]\), 則\(idom[X]=semi[X]\)
如果\(semi[Z] \ne semi[X]\), 則\(idom[X] = idom[Z]\)
具體實現
為了方便實現, semi[i]在代碼中的含義與講解中有所出入. 它表示的是一個點的半支配點的dfn值.
對于求半必經點與必經點我們都需要處理一個問題, 就是對于一個節點\(X\)的前驅\(Y\), 我們需要計算\(Y\)在搜索樹上所有dfn值大于\(dfn[X]\)的祖先中semi值最小的一個, 我們可以按dfn從大到小的順序處理, 使用帶權并查集維護, 記錄每一個點到并查集的根節點的路徑上semi值最小的點, 這樣處理到節點\(X\)值時所有dfn值比\(X\)大的點都被維護起來了.
這樣我們就能夠在\(O((n+m) \cdot \alpha(n))\)時間內解決這個問題.
放一下代碼吧:
轉載于:https://www.cnblogs.com/ZeonfaiHo/p/6594642.html
總結
- 上一篇: Bootstrap File Input
- 下一篇: Spring Boot由jar包转成wa