1. 程式人生 > >淺談Tarjan求強連通分量

淺談Tarjan求強連通分量

首先,要知道什麼是強連通分量,這個還是比較簡單的吧。
那麼就談談Tarjan求強連通分量。

概率(扯淡)

首先,Tarjan演算法是基於對圖深度優先搜尋(DFS)的演算法,每個強連通分量為搜尋樹中的一個子樹。
搜尋時把當前搜尋樹中未處理的結點加入一個棧,回溯時可以判斷棧頂中的結點是否構成一個強連通分量。
DFS的過程中遇到的四種邊:
1、樹枝邊:DFS時經過的邊,即DFS搜尋樹上的邊。
2、前向邊:與DFS方向一致,從某個結點指向其某個子孫的邊
3、後向邊:與DFS方向相反,從某個結點指向其某個祖先的邊
4、橫叉邊:從某個結點指向搜尋樹中另一個子樹中的某個結點的邊。
定義DFN(n)為結點u的搜尋次序編號(時間戳)可以理解為是第幾個dfs到的,low(u)為 u 或u的子樹能夠追溯到的最早的棧中結點的DFN值。其定義可以得出:
如果(u,v)為樹枝邊,u 為 v 的父節點,則low( u ) = min(low( u ), low( v ) );
如果(u,v)為後向邊或指向棧中結點的橫叉邊,則low( u ) = min (low( u ),dfn( v ));
然後如果在回溯的時候DFN[u]=low[u] 那麼久可以退棧了,就是以u為根的搜尋子樹上所有還在棧中的結點是一個強連通分量,就是u和在u之後搜尋到的可以組成一個強連通分量。

演算法流程(亂搞+魔改)

就是先一個for列舉每個點為起點(搜尋樹的樹根)看當前是否被搜尋過,沒有就進去亂搞一通,然後就可以了(就是利用上面的那兩個得出來的東東)

這題接近於裸題,就多記一個數量就行了
程式碼:

#include<bits/stdc++.h>
#define N 100050
using namespace std;
struct edge{//鏈式前向星
    int v,next;
}e[N];
int p[N],eid;
void add(int u,int v){
    eid++;
    e[eid].v=v;
    e[eid].next=p[u];
    p[u]=eid;
}
int
dfn[N],low[N],bel[N],sta[N],bl[N],sz,tot,top; void dfs(int x){ //Tarjan 求強連通分量 dfn[x]=++sz; low[x]=dfn[x]; //一開始low[x]就是等於dfn[x](暫時追溯到最早的就是自己) sta[++top]=x; for(int i=p[x];i;i=e[i].next){ int v=e[i].v; if(!dfn[v]){ //第一個得出來的 dfs(v); low[x]=min(low[x
],low[v]); } else if(!bel[v]) low[x]=min(low[x],dfn[v]); } if(low[x]==dfn[x]){ bel[x]=++tot; bl[tot]++; //記一下強連通分量的大小 while(sta[top]!=x){ bel[sta[top]]=tot; --top; bl[tot]++; } --top; } } int n,m; int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ int u,v,t; scanf("%d%d%d",&u,&v,&t); if(t==2) add(v,u); add(u,v); } for(int i=1;i<=n;i++){ if(!dfn[i]) dfs(i); } int ma=0,mai=0; for(int i=1;i<=tot;i++){ if(bl[i]>ma) ma=bl[i],mai=i; } for(int i=1;i<=n;i++){ if(ma==bl[bel[i]]){ mai=bel[i]; break; } } printf("%d\n",ma); for(int i=1;i<=n;i++){ if(bel[i]==mai) printf("%d ",i); } return 0; } //啦啦啦啦(防偽標識)

蛤?就沒了
沒了……

參考資料:
《資訊學奧賽一本通·提高篇》