Quetion : 判斷in_stack[]有什麼意義?
<0>
if(dfn[i] != 0) low[u] = min(low[u], dfn[v]);
當只是強連通搞縮點時,可以換爲min(low[u], low[v]),因爲縮點只要把相鄰的一塊搞跟min(low[xxx])一塊兒就行了;
但是搞雙連通就不可以了,因爲雙連通對割點/橋的判定需要用到low[v],後面略..
無向圖的Tarjan判斷邊是否出現過僅僅爲了防止逆向邊的使用...我還以爲有什麼神奇的作用呢...-_-|| 如果是有向圖的話就免了
當重邊只算一條的時侯,既可以用上訴的判邊法,也可用判點法,即記錄每個節點在dfs樹的父節點,然後每次都判斷一下fa[u] == v ?
<1> 求強連通塊 //應用: 縮點
void Tarjan(int u)
{
dfn[u] = low[u] = Index++;
S[++top] = i;
for(int k = head[u]; k != -1; k = edge[k].next) {
//TODO 什麼時候要判斷點v或者邊k是否出現過?
int v = edge[k].v;
if(!dfn[v]) {
Tarjan(v);
low[u] = min(low[u], low[v]);
} else if(in_stack[v]) low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u]) {
Order++;
int x;
do {
x = S[top--];
in_stack[x] = false;
belong[x] = Order;
} while(x != u);
}
}
void solve()
{
for(int i = 1; i <= n; i++) if(!dfn[i]) Tarjan(i);
}
<2> 割點u的充要條件: (1) u爲樹根,且u有多於一個子樹。
(2) u不爲樹根,且滿足存在(u,v)爲樹枝邊(或稱父子邊,即u爲v在搜索樹中的父親),使得DFS(u)<=Low(v)。
橋(u,v)的充要條件: 當且僅當(u,v)爲樹枝邊,且滿足DFS(u)<Low(v)
<3>[求雙連通分支]
雙連通分支: 圖的極大雙連通子圖。我喜歡稱之爲“塊”。
下面要分開討論點雙連通分支與邊雙連通分支的求法。
對於點雙連通分支,實際上在求割點的過程中就能順便把每個點雙連通分支求出。建立一個棧,存儲當前雙連通分支,在搜索圖時,每找到一條樹枝邊或後向邊(非橫叉邊),就把這條邊加入棧中。如果遇到某時滿足DFS(u)<=Low(v),說明u是一個割點,同時把邊從棧頂一個個取出,直到遇到了邊(u,v),取出的這些邊與其關聯的點,組成一個點雙連通分支。割點可以屬於多個點雙連通分支,其餘點和每條邊只屬於且屬於一個點雙連通分支。
對於邊雙連通分支,求法更爲簡單。只需在求出所有的橋以後,把橋邊刪除,原圖變成了多個連通塊,則每個連通塊就是一個邊雙連通分支。橋不屬於任何一個邊雙連通分支,其餘的邊和每個頂點都屬於且只屬於一個邊雙連通分支。
*** 上面方法是用棧來存邊 *** 其實也可以用棧來存點來搞的 ***
當每次dfn[u] <= low[v]時就是遇到一個割點,這個時候就從棧頂把元素一個一個取出,直到點v。注意,是點v,不是點u。因爲點u是割點,可能存在於多個雙連通分量裏面,所以它要繼續呆在棧裏面。當然,當前雙連通分量是包括u的,雖然不取出來,但還是要包括的。
所以說,其實存邊和存點是一樣的。
<4>[構造雙連通圖]
方法簡述:縮點,對縮之後的點建圖,統計其葉節點個數leave,則構造雙連通圖所需要的邊爲(leave+1)/2.
一個有橋的連通圖,如何把它通過加邊變成邊雙連通圖?方法爲首先求出所有的橋,然後刪除這些橋邊,剩下的每個連通塊都是一個雙連通子圖。把每個雙連通子圖收縮爲一個頂點,再把橋邊加回來,最後的這個圖一定是一棵樹,邊連通度爲1。
統計出樹中度爲1的節點的個數,即爲葉節點的個數,記爲leaf。則至少在樹上添加(leaf+1)/2條邊,就能使樹達到邊二連通,所以至少添加的邊數就是(leaf+1)/2。具體方法爲,首先把兩個最近公共祖先最遠的兩個葉節點之間連接一條邊,這樣可以把這兩個點到祖先的路徑上所有點收縮到一起,因爲一個形成的環一定是雙連通的。然後再找兩個最近公共祖先最遠的兩個葉節點,這樣一對一對找完,恰好是(leaf+1)/2次,把所有點收縮到了一起。
題目:poj_3352
<0>
if(dfn[i] != 0) low[u] = min(low[u], dfn[v]);
當只是強連通搞縮點時,可以換爲min(low[u], low[v]),因爲縮點只要把相鄰的一塊搞跟min(low[xxx])一塊兒就行了;
但是搞雙連通就不可以了,因爲雙連通對割點/橋的判定需要用到low[v],後面略..
無向圖的Tarjan判斷邊是否出現過僅僅爲了防止逆向邊的使用...我還以爲有什麼神奇的作用呢...-_-|| 如果是有向圖的話就免了
當重邊只算一條的時侯,既可以用上訴的判邊法,也可用判點法,即記錄每個節點在dfs樹的父節點,然後每次都判斷一下fa[u] == v ?
<1> 求強連通塊 //應用: 縮點
void Tarjan(int u)
{
dfn[u] = low[u] = Index++;
S[++top] = i;
for(int k = head[u]; k != -1; k = edge[k].next) {
//TODO 什麼時候要判斷點v或者邊k是否出現過?
int v = edge[k].v;
if(!dfn[v]) {
Tarjan(v);
low[u] = min(low[u], low[v]);
} else if(in_stack[v]) low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u]) {
Order++;
int x;
do {
x = S[top--];
in_stack[x] = false;
belong[x] = Order;
} while(x != u);
}
}
void solve()
{
for(int i = 1; i <= n; i++) if(!dfn[i]) Tarjan(i);
}
<2> 割點u的充要條件: (1) u爲樹根,且u有多於一個子樹。
(2) u不爲樹根,且滿足存在(u,v)爲樹枝邊(或稱父子邊,即u爲v在搜索樹中的父親),使得DFS(u)<=Low(v)。
橋(u,v)的充要條件: 當且僅當(u,v)爲樹枝邊,且滿足DFS(u)<Low(v)
<3>[求雙連通分支]
雙連通分支: 圖的極大雙連通子圖。我喜歡稱之爲“塊”。
下面要分開討論點雙連通分支與邊雙連通分支的求法。
對於點雙連通分支,實際上在求割點的過程中就能順便把每個點雙連通分支求出。建立一個棧,存儲當前雙連通分支,在搜索圖時,每找到一條樹枝邊或後向邊(非橫叉邊),就把這條邊加入棧中。如果遇到某時滿足DFS(u)<=Low(v),說明u是一個割點,同時把邊從棧頂一個個取出,直到遇到了邊(u,v),取出的這些邊與其關聯的點,組成一個點雙連通分支。割點可以屬於多個點雙連通分支,其餘點和每條邊只屬於且屬於一個點雙連通分支。
對於邊雙連通分支,求法更爲簡單。只需在求出所有的橋以後,把橋邊刪除,原圖變成了多個連通塊,則每個連通塊就是一個邊雙連通分支。橋不屬於任何一個邊雙連通分支,其餘的邊和每個頂點都屬於且只屬於一個邊雙連通分支。
*** 上面方法是用棧來存邊 *** 其實也可以用棧來存點來搞的 ***
當每次dfn[u] <= low[v]時就是遇到一個割點,這個時候就從棧頂把元素一個一個取出,直到點v。注意,是點v,不是點u。因爲點u是割點,可能存在於多個雙連通分量裏面,所以它要繼續呆在棧裏面。當然,當前雙連通分量是包括u的,雖然不取出來,但還是要包括的。
所以說,其實存邊和存點是一樣的。
<4>[構造雙連通圖]
方法簡述:縮點,對縮之後的點建圖,統計其葉節點個數leave,則構造雙連通圖所需要的邊爲(leave+1)/2.
一個有橋的連通圖,如何把它通過加邊變成邊雙連通圖?方法爲首先求出所有的橋,然後刪除這些橋邊,剩下的每個連通塊都是一個雙連通子圖。把每個雙連通子圖收縮爲一個頂點,再把橋邊加回來,最後的這個圖一定是一棵樹,邊連通度爲1。
統計出樹中度爲1的節點的個數,即爲葉節點的個數,記爲leaf。則至少在樹上添加(leaf+1)/2條邊,就能使樹達到邊二連通,所以至少添加的邊數就是(leaf+1)/2。具體方法爲,首先把兩個最近公共祖先最遠的兩個葉節點之間連接一條邊,這樣可以把這兩個點到祖先的路徑上所有點收縮到一起,因爲一個形成的環一定是雙連通的。然後再找兩個最近公共祖先最遠的兩個葉節點,這樣一對一對找完,恰好是(leaf+1)/2次,把所有點收縮到了一起。
題目:poj_3352