標籤:memset mem == 資訊 space ace 數組 head 記錄
求強聯通分量有很多種。 《C++資訊學奧賽一本通》 中講過一個dfs求強聯通分量的演算法Kosdaraju,為了騙字數我就待會簡單的說說。然而我們這篇文章的主體是Tarjan,所以我肯定說完之後再讚揚一下Tarjan大法好是不是
首先我們講一下強聯通分量
強聯通分量指的是圖的一個子圖。在這個子圖中,任意兩個節點都可以互相到達。從定義上我們就可以看出是一個有向圖來,因為任意一個無向圖都符合該定義。
而它的標準定義是:有向圖中任意兩點都聯通的最大子圖。
咳咳,首先慶祝一下哈——本人部落格的第一張圖。繪圖曆時3分鐘。
在咱們舉的例子中,可以看出1 、2 、3 、5 通過邊可以相互到達,它們算一個強聯通分量,但4卻被它們隔絕在外。可以看出,從4點出發不能到達任意一個點。所以它單個節點也算一個強聯通分量。所以圖中的強聯通分量有兩個:一個是1-2-3-5,一個是4。
ok看完了強聯通分量是什麼我們就講一下Kosaraju。
這個演算法的思路是,對圖進行DFS並記錄每個點的退出順序。再構造反圖(就是有向邊的方向全都反過來),按照退出順序的逆序DFS反圖,對得到的點進行染色即為強聯通分量。
講完思路開始類比。以起點1為起點遍曆順序如下:
[ 1 2 3 5 4 5 3 2 4 4 1 ]
加粗斜體外帶底線的部分就是本圖的退出順序。
於是我們得到這樣一個數組:[ 5 3 2 4 1 ] 。按照這個數組的逆序對反圖遍曆得到:
[ 5 3 2 1 退出 4 退出 ]
即得到要求的兩個強聯通分量。
還要兩遍DFS,麻煩的一比。看我大Tarjan一遍DFS就能求出強聯通分量
首先我們要明確Tarjan要用到的兩個數組:dfn[] 和 low[]
dfn指的是在DFS過程中訪問到該點的順序。從1開始DFS全圖,那麼1的dfn值就是1,2的dfn值是2,5的dfn值是4,4的dfn值是5。剩下的一個類推
那麼low呢?low指的是如果逆著DFS序往前回溯,該節點最早是由哪個節點走過來的。
比如在中2 、3 、5 、4 最早都是由1走過來的,所以它們的low值都是1
下面貼出dfn和low的演算法
每次dfs(點u){
dfn[u] = 進入 dfs() 函數的次數 (自己定義一個時間戳記錄 如 timee)
枚舉與其相鄰的點v{
如果 沒有 訪問過點v { ( 就是dfs樹上的樹邊 )
dfs(v);
如果 v 能追溯 到 比“u 追溯到的最早的點” 更早的點;
那麼 u 就能 通過 v 來追溯到 那個點;
low[u]=min(low[u],low[v]);
}
如果 訪問過點v && v在棧中
low[u]=min(low[u],dfn[v]);
}
縮點
}
上面那些虛擬碼是從偉大的GeneralLiu那裡帶過來的,在此先%%%
然後 假設我們走到一個節點i,發現這個i不能繼續擴充了,也就是dfn[i]==low[i]
於是我們開始往回走。往回走的過程中,我們就把和它一個分量的節點進行染色,給它們統一的標記。 最後統計有多少種不同的標記即是強聯通分量個數
luogu的一道題燒錄光碟片非常好,可以用於練手。
放代碼
#include<iostream>#include<cstring>using namespace std;int head[10000],num;struct Edge{ int next,to;}edge[100000];int stack[10000],top;int color[10000],cnt;int dfn[10000],low[10000];int ID;bool jd[10000];int vis[10000];inline void add(int from,int to){ edge[++num]={head[from],to}; head[from]=num;}void tarjan(int x){ dfn[x]=++ID; low[x]=ID; jd[x]=1; stack[++top]=x; for(int i=head[x];i;i=edge[i].next){ int to=edge[i].to; if(!dfn[to]){ tarjan(to); low[x]=min(low[x],low[to]); } else if(jd[to]) low[x]=min(low[x],dfn[to]); } if(dfn[x]==low[x]){ jd[x]=0; color[x]=++cnt; while(stack[top]!=x){ color[stack[top--]]=cnt; jd[stack[top+1]]=0; color[stack[top+1]]=cnt; } top--; } }int main(){ int n; cin>>n; int x; for(int i=1;i<=n;++i){ while(cin>>x&&x!=0){ add(i,x); } } for(int i=1;i<=n;++i){ if(!dfn[i]) tarjan(i); } memset(jd,0,sizeof(jd)); for(int i=1;i<=n;++i){ for(int j=head[i];j;j=edge[j].next){ if(color[i]!=color[edge[j].to]){ jd[color[edge[j].to]]=1; } } } int ans=0; for(int i=1;i<=cnt;++i) if(!jd[i]) ans++; cout<<ans<<endl; return 0;}
Tarjan的強聯通分量