有向圖強連通份量:在有向圖G中,若是兩個頂點vi,vj間(vi>vj)有一條從vi到vj的有向路徑,同時還有一條從vj到vi的有向路徑,則稱兩個頂點強連通(strongly connected)。若是有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。有向圖的極大強連通子圖,稱爲強連通份量。ios
經過肉眼能夠很直觀地看出1-2-3是一組強連通份量,但很遺憾,機器並無眼睛,因此該怎麼判斷強連通份量呢?算法
上面那張圖,咱們對它進行dfs遍歷。能夠注意到紅邊很是特別,由於若是按照遍歷時間來分類的話,其餘邊都指向在本身以後被遍歷到的點,而紅邊指向的則是比本身先被遍歷到的點。數組
從一個點出發,一直向下遍歷,而後忽得找到一個點,那個點居然有條指回這一個點的邊!post
那麼想必這個點可以從自身出發再回到自身spa
想必這個點和其餘向下遍歷的該路徑上的全部點構成了一個環,code
想必這個環上的全部點都是強聯通的。blog
可是咱們想要知道的是這個圖的強聯通份量。get
只須要知道這個點u下面的全部子節點有沒有連着u的祖先就好了。string
但是咱們怎麼知道這個點u它下面的全部子節點必定是都與他強聯通的呢?it
這彷佛是不對的,這個點u之下的全部點不必定都強聯通
那麼怎麼在退回到這個點的時候,知道全部和這個點u構成強連通份量的點呢?
開個棧記錄就好了
具體實現還要看代碼。
咱們須要幾個數組來完成Tarjan算法
接下來咱們開始遍歷每個點。
遍歷到u時,首先初始化u的dfn和low都爲當前節點的遍歷序號。
而後將u打入棧中,去遍歷它的每一個子節點,若是它的子節點沒有被訪問到,就訪問,若是訪問到了,就不去訪問。u全部子節點的最小low值,即爲u的low值
在u及其子節點訪問完後,它的low值不再會變化。
若是作完了上面這一切,咱們如何判斷u及其一些子節點可否變成一個強聯通份量。
注意:這裏,若是u的子節點已經本身成爲了一個強聯通份量,它已經出棧
試想一下,若是這時候u的dfn和low值相同,會怎麼樣?
兩種狀況:
若是是第1種狀況,那麼比u後遍歷到的節點,在棧中的,必定能夠和u構成一個強連通份量。
若是是第二種狀況,表明着u的全部子節點都沒法和u構成強連通份量,也就不在棧中。
因此,若是出現這種狀況(u的dfn和low值相同),那麼在棧中的u上面的節點(不是都說棧頂嗎,咱們假設這個棧是站立的)和u必定能構成強連通份量。
不然,u的low值必定比dfn值小,這時必定會存在一個u的祖先節點,或是另外一分支的節點。
例題:https://www.luogu.com.cn/problem/P3387
代碼:
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #include<sstream> #include<queue> #include<map> #include<vector> #include<set> #include<deque> #include<cstdlib> #include<ctime> #define dd double #define ll long long #define ld long double #define ull unsigned long long #define N 20000 #define M 100100 using namespace std; inline int Max(int a,int b){ return a>b?a:b; } inline int Min(int a,int b){ return a>b?b:a; } struct edge{ int to,next; inline void intt(int to_,int next_){ to=to_;next=next_; } }; edge li[M],li2[M]; int head[N],tail,head2[N],tail2; inline void add(int from,int to){ li[++tail].intt(to,head[from]); head[from]=tail; } inline void add2(int from,int to){ li2[++tail2].intt(to,head2[from]); head2[from]=tail2; } int dfn[N],low[N],stack[N],top,deep,co_num,co[N]; bool vis[N]; inline void tarjan(int u){ dfn[u]=++deep;low[u]=deep;stack[++top]=u;vis[u]=1; for(int x=head[u];x;x=li[x].next){ int to=li[x].to; if(!dfn[to]){ tarjan(to); low[u]=Min(low[u],low[to]); } else if(vis[to]) low[u]=Min(low[u],low[to]); } if(low[u]==dfn[u]){ co[u]=++co_num; vis[u]=0; while(stack[top]!=u){ co[stack[top]]=co_num; vis[stack[top]]=0; top--; } top--; } } int n,m,a[N],p[N],f[N]; inline void dfs1(int u){ vis[u]=1; for(int x=head[u];x;x=li[x].next){ int to=li[x].to; if(co[to]!=co[u]) add2(co[u],co[to]); if(!vis[to]) dfs1(to); } } inline int dfs2(int u){ if(f[u]) return f[u]; for(int x=head2[u];x;x=li2[x].next){ int to=li2[x].to; f[u]=Max(f[u],dfs2(to)); } f[u]+=p[u]; return f[u]; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=m;i++){ int from,to; scanf("%d%d",&from,&to); add(from,to); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); memset(vis,0,sizeof(vis)); for(int i=1;i<=n;i++) if(!vis[i]) dfs1(i); for(int i=1;i<=n;i++) p[co[i]]+=a[i]; memset(vis,0,sizeof(vis)); for(int i=1;i<=co_num;i++) if(!f[i]) f[i]=dfs2(i); int maxx=-1; for(int i=1;i<=co_num;i++) maxx=Max(maxx,f[i]); printf("%d",maxx); return 0; }
這個題博主作麻煩了。實際上能夠在原來的路徑上直接改圖,可是出題人沒有像z*c同樣愛卡空間和時間。因此能過。
tarjan算法仍是能夠求割點的。
在一個無向圖中,若是有一個頂點集合,刪除這個頂點集合以及這個集合中全部頂點相關聯的邊之後,圖的連通份量增多,就稱這個點集爲割點集合。
簡單點說就是你把割點去掉後,這個圖就好似被撕成了幾個部分同樣,各個部分之間再也不聯通。
仍是同樣,咱們繼續用上面的dfn和low數組,可是這裏,low數組的定義會有變化
定義以下:最先能繞到的點
由於是無向圖,因此不存在祖先。
同時,若是比u晚遍歷到的點的low值還比u的dfn小的話(或者等於),那麼去掉點u,確定會多一個聯通圖。
同時還要注意判斷dfs樹的根節點,若是根節點兒子數目大於等於2,則根節點也是一個割點。
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #include<sstream> #include<queue> #include<map> #include<vector> #include<set> #include<deque> #include<cstdlib> #include<ctime> #define dd double #define ll long long #define ld long double #define ull unsigned long long #define N 30010 #define M 200100 using namespace std; struct edge{ int to,next; inline void intt(int to_,int next_){ to=to_;next=next_; } }; edge li[M]; int head[N],tail; inline int Min(int a,int b){ return a>b?b:a; } inline void add(int from,int to){ li[++tail].intt(to,head[from]); head[from]=tail; } int dfn[N],low[N],deep; bool iscut[N]; inline void tarjan(int u,int fa){ dfn[u]=++deep;low[u]=deep; int child=0; for(int x=head[u];x;x=li[x].next){ int to=li[x].to; if(!dfn[to]){ child++; tarjan(to,u); low[u]=Min(low[u],low[to]); if(low[to]>=dfn[u]) iscut[u]=1; } else low[u]=Min(low[u],dfn[to]);//尤爲注意這句話 } if(fa==-1&&child==1) iscut[u]=0; } int n,m; int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ int from,to; scanf("%d%d",&from,&to); add(from,to); add(to,from); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,-1); int ans=0; for(int i=1;i<=n;i++) if(iscut[i]) ans++; printf("%d\n",ans); for(int i=1;i<=n;i++) if(iscut[i]) printf("%d ",i); }