淺談Tarjan求強連通分量
阿新 • • 發佈:2019-02-02
首先,要知道什麼是強連通分量,這個還是比較簡單的吧。
那麼就談談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;
}
//啦啦啦啦(防偽標識)
蛤?就沒了
沒了……
參考資料:
《資訊學奧賽一本通·提高篇》