標籤:get tle strong ext lap tarjan min can 互連
一.演算法簡介
Tarjan 演算法一種由Robert Tarjan提出的求解有向圖強連通分量的演算法,它能做到線性時間的複雜度。
我們定義:
如果兩個頂點可以相互連達,則稱兩個頂點強連通(strongly connected)。如果有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。有向圖的極大強連通子圖,稱為強連通分量(strongly connected components)。
例如:在中,{1 , 2 , 3 , 4 } , { 5 } , { 6 } 三個地區可以相互連通,稱為這個圖的強連通分量。
Tarjan演算法是基於對圖深度優先搜尋的演算法,每個強連通分量為搜尋樹中的一棵子樹。搜尋時,把當前搜尋樹中未處理的節點加入一個堆棧,回溯時可以判斷棧頂到棧中的節點是否為一個強連通分量。
再Tarjan演算法中,有如下定義。
DFN[ i ] : 在DFS中該節點被搜尋的次序(時間戳記)
LOW[ i ] : 為i或i的子樹能夠追溯到的最早的棧中節點的序號
當DFN[ i ]==LOW[ i ]時,為i或i的子樹可以構成一個強連通分量。
二.演算法圖示
以1為Tarjan 演算法的起始點,
順次DFS搜到節點6
回溯時發現LOW[ 5 ]==DFN[ 5 ] , LOW[ 6 ]==DFN[ 6 ] ,則{ 5 } , { 6 } 為兩個強連通分量。回溯至3節點,拓展節點4.
拓展節點1 , 發現1再棧中更新LOW[ 4 ],LOW[ 3 ] 的值為1
回溯節點1,拓展節點2
自此,Tarjan Algorithm 結束,{1 , 2 , 3 , 4 } , { 5 } , { 6 } 為圖中的三個強連通分量。
不難發現,Tarjan Algorithm 的時間複雜度為O(E+V).
學習處
模板:
1 void dfs(int u) 2 { 3 times++;//記錄dfn順序 4 dfn[u]=times;//賦值 5 low[u]=times;//先賦初值 6 vis[u]=true;//vis[i]用來判斷i是否搜尋過; 7 insta[u]=true;//表示是否在棧中,true為在棧中; 8 stack[top]=u;//棧頂 9 top++;10 for(int i=head[u];i!=-1;i=edge[i].next)// 以建圖順序枚舉此點所連的邊 11 {12 int v=edge[i].to;//搜尋到的點 13 if(!vis[v])//如果未搜尋過即未入棧 14 {15 dfs(v);//繼續以此點進行深搜 16 low[u]=min(low[u],low[v]);//更新low值,此邊為樹枝邊所以比較u此時的17 } // low值(未更新時就是其dfn值)和v的low值18 else 19 if(insta[v]==true)//如果搜尋過且在棧中,說明此邊為後向邊或棧中橫叉邊20 {21 low[u]=min(low[u],dfn[v]);//更新low值,比較u此時的low值和v的dfn值 22 }23 }24 25 if(low[u]==dfn[u])//相等說明找到一個強連通分量 26 {27 while(top>0&&stack[top]!=u)//開始退棧一直退到 u為止 28 {29 top--;30 insta[stack[top]]=false;31 }32 }33 }
View Code
例題:
poj2186 - Popular Cows
題目大意:題目大意是:在一個牧群中,有N個奶牛,給定M對關係(A,B)表示A仰慕B,而且仰慕關係有傳遞性,問被所有奶牛(除了自己)仰慕的奶牛個數
解題思路:找出所有的連通分量,如果只有一個連通分量的出度為0,那麼輸出那個連通分量的點的個數即可,如果不唯一就輸出0
因為連通分量的出度有兩個的話,那麼肯定至少存在一頭牛不仰慕另外一頭牛,所以我們至少保證要出度為0的連通分量唯一
解題思路:
1、用Tarjan求雙連通分量然後縮成點。這些點會形成一棵樹。
2、求樹上的節點有多少個出度為零,如果有一個就輸出那個點裡包含的所有點(因為是縮點出來的樹)。
注意:
1、給出的圖會有不連通的可能,如果那樣肯定輸出零。因為不連通肯定不會有所有其他牛認為某隻牛很牛的情況出現。
2、如果縮點後有多個出度為零的點,那麼輸出零。因為這樣圖雖然聯通了,但是還是不會出現所有其他牛認為某隻牛很牛的情況(自己畫一下就知道啦)。
求強連通分量主要是為了簡化圖的構造,如果分量外的一個點能到達分量內的其中一個點,那麼它必定能到達分量內的所有點,所以某種程度上,強連通分量可以簡化成一個點。
#include <stdio.h>#include <string.h>const int MAXN = 10005;const int MAXM = 100005;struct node{ int to,next;} edge[MAXM];int n,m,head[MAXN],dfn[MAXN],low[MAXN],stack1[MAXN],num[MAXN],du[MAXN],vis[MAXN],cnt,time,top,cut;void init(){ memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(head,-1,sizeof(head)); memset(vis,0,sizeof(vis)); memset(num,0,sizeof(num)); memset(du,0,sizeof(du)); cnt=0; time=1; top=0; cut=0;}void addedge(int u,int v){ edge[cnt].to=v; edge[cnt].next=head[u]; head[u]=cnt; cnt++;}int min(int a,int b){ if(a>b)a=b; return a;}void dfs(int u,int fa){ dfn[u]=time; low[u]=time; time++; vis[u]=1; stack1[top]=u; top++; for(int i=head[u]; i!=-1; i=edge[i].next) { int v=edge[i].to; if(!vis[v]) { dfs(v,u); low[u]=min(low[u],low[v]); } else if(vis[v]) { low[u]=min(low[u],dfn[v]); } } if(low[u]==dfn[u]) { cut++; while(top>0&&stack1[top]!=u) { top--; vis[stack1[top]]=2; num[stack1[top]]=cut; } }}int main(){ int i,u,v; while(scanf("%d%d",&n,&m)!=EOF) { init(); for(i=0; i<m; i++) { scanf("%d%d",&u,&v); addedge(u,v); } for(int i=1; i<=n; i++) { if(!vis[i]) { dfs(i,0); } } for(i=1; i<=n; i++) { for(int j=head[i]; j!=-1; j=edge[j].next) { if(num[i]!=num[edge[j].to]) { du[num[i]]++; } } } int sum=0,x; for(i=1; i<=cut; i++) { if(!du[i]) { sum++; x=i; } } if(sum==1) { sum=0; for(i=1; i<=n; i++) { if(num[i]==x) { sum++; } } printf("%d\n",sum); } else { puts("0"); } } return 0;}
View Code
tarjan演算法,一個關於 圖的聯通性的神奇演算法