日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

支配树学习笔记

發布時間:2025/7/14 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 支配树学习笔记 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

@(學習筆記)[支配樹]

簡介

什么是支配樹?支配樹是什么?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\)?
這當然是不正確的, 我們可以從兩個方面來說明它的錯誤:

  • 刪掉割點不一定使\(r\)無法到達\(t\)
    ?
    這個圖中點\(u\)是關鍵點(刪掉后圖聯通塊個數增加)
    并且\(u\)\(r\to t\)的路徑上, 然而刪掉點\(u\)\(r\)仍然可以到達\(t\)
  • 圖中不一定存在割點
    ?
    在這個圖中不存在任何割點
    所以我們沒有辦法使用割點來解決這個問題.
  • 簡化問題


  • 對于一棵樹, 我們用r表示根節點, u表示樹上的某個非根節點. 很容易發現從\(r\to u\)路徑上的所有點都是支配點, 而\(idom[u]\)就是\(u\)的父節點.
    這個可以在\(O(n)\)的時間內實現.
  • DAG(有向無環圖)
    因為是有向無環圖, 所以我們可以按照拓撲序構建支配樹.
    假設當前我們構造到拓撲序中第\(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算法的實現.

    首先來介紹一些這個算法的大概步驟:

  • 對圖進行DFS(深度優先遍歷)并求出搜索樹和DFS序. 這里我們用dfn[x]表示
    \(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值最小的點的編號.
    我們可以更書面一點的描述這個定理:

  • 對于一個節點\(Y\)考慮所有能夠到達它的節點, 設其中一個為\(X\). 若\(dfn[X]<dfn[Y]\), 則\(X\)\(Y\)的一個半必經點
    ?
  • \(dfn[X]>dfn[Y]\), 那么對于\(X\)在搜索樹中的祖先\(Z\)(包括\(X\)), 如果滿足\(dfn[Z]>dfn[Y]\)那么\(semi[Z]\)也是Y的半必經點
    ?
  • 在這些必經點中, 我們僅需要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))\)時間內解決這個問題.
    放一下代碼吧:

    #include<cstring> #include<vector> #include<algorithm> #include<deque>using namespace std;const int N = 1 << 17, M = 1 << 19;struct graph {int head[N], top;vector<int> pre[N];struct edge{int v, nxt, w;}edg[M << 1];inline void init(){memset(head, -1, sizeof(head));top = 0;for(int i = 0; i < N; ++ i)pre[i].clear();}inline void addEdge(int u, int v){edg[top].v = v, edg[top].nxt = head[u];head[u] = top ++;pre[v].push_back(u);}int dfn[N], idx[N], clk;vector<int> bck[N]; //bck[i]記錄以i為半支配點的節點的集合, 以方便計算idm[].int idm[N], sdm[N];int preOnTree[N];struct disjointSet{int pre[N], w[N];void access(int u, int *sdm){if(pre[u] == u)return;access(pre[u] , sdm);if(sdm[w[pre[u]]] < sdm[w[u]])w[u] = w[pre[u]];pre[u] = pre[pre[u]];}}st;void dfs(int u){dfn[u] = clk;idx[clk ++] = u;for(int i = head[u]; ~ i; i = edg[i].nxt)if(! (~ dfn[edg[i].v]))preOnTree[edg[i].v] = u, dfs(edg[i].v);}inline void tarjan(int s){memset(dfn, -1, sizeof(dfn));clk = 0;for(int i = 0; i < N; ++ i)bck[i].clear();for(int i = 0; i < N; ++ i)st.pre[i] = st.w[i] = i;preOnTree[s] = -1;dfs(s);for(int i = 0; i < N; ++ i)idm[i] = i, sdm[i] = dfn[i];for(int i = clk - 1; ~ i; -- i){int u = idx[i];for(vector<int>::iterator p = bck[u].begin(); p != bck[u].end(); ++ p){int v = *p;st.access(v, sdm);idm[v] = sdm[st.w[v]] == sdm[v] ? u : st.w[v];}bck[u].clear();if(! i){idm[idx[i]] = sdm[idx[i]] = -1;break;}for(vector<int>::iterator p = pre[u].begin(); p != pre[u].end(); ++ p)if(~ dfn[*p]) //考慮到原圖可能不聯通{int v = *p;st.access(v, sdm);sdm[u] = min(sdm[u], sdm[st.w[v]]);}bck[idx[sdm[u]]].push_back(u);st.pre[u] = preOnTree[u];}for(int i = 1; i < clk; ++ i)idm[idx[i]] = idm[idx[i]] == idx[sdm[idx[i]]] ? idm[idx[i]] : idm[idm[idx[i]]];} };

    轉載于:https://www.cnblogs.com/ZeonfaiHo/p/6594642.html

    總結

    以上是生活随笔為你收集整理的支配树学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。