这几题练习强连通分量的Tarjan求法:
在一个有向图中,如果两个点a,b之间存在a->b的路径以及b->a的路径,则称a与b在同一强联通分量(SCC,strongly connected component)之中。因此可以将图划分为几个子图,每一个子图中都是一个极大强联通分量。如果将所有的强连通分量都缩成一个点,原图就变成了一个DAG(有向无环图)。
可以用Tarjan算法、Kosaraju算法、Gabow算法等来求图的强联通分量,其中的Tarjan算法在求图的割点、割边等方面也有很广泛的应用。
Tarjan算法是基于深度优先搜索的,只需要一次深搜和一个栈即可求出有向图的SCC。
在DFS过程中,每当遍历到一个节点,就将其压栈,同时为每个点都标记颜色,初始所有的点为白色,正在DFS的点为灰色,已经搜索完成的点为黑色。同时用dfn数组记录每个点的时间戳(即搜索的次序)。下面给出low数组的定义:
初始:dfn[u] = low[u],如果有边u->v,则:
第一题:Hoj 1520 The Bottom of a Graph
链接:http://acm.hit.edu.cn/hoj/problem/view?id=1520
求强联通分量后缩点,求出度为0的点。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <vector> #include <queue> #include <stack> #include <algorithm> using namespace std; #define Maxn 5005 #define Maxm 50005 struct Edge { int a,b; }edge[Maxm]; int first[Maxn]; int next[Maxm]; int total; int sccno[Maxn]; bool instack[Maxn]; int dfn[Maxn]; int low[Maxn]; int dfs_clock; int scc_cnt; int in[Maxn],out[Maxn]; stack <int> st; void addEdge(int a,int b) { total++; edge[total].a = a;edge[total].b = b; next[total] = first[a]; first[a] = total; } void tarjan(int u) { dfn[u] = low[u] = ++dfs_clock; st.push(u); instack[u] = true; for(int i=first[u];i!=-1;i=next[i]) { int v = edge[i].b; if(!dfn[v]) { tarjan(v); low[u] = min(low[u],low[v]); } else if(instack[v]) { low[u] = min(low[u],dfn[v]); } } if(dfn[u] == low[u]) { scc_cnt++; while(1) { int v = st.top(); st.pop(); instack[v] = false; sccno[v] = scc_cnt; if(u == v) break; } } } void find_scc(int n) { scc_cnt = dfs_clock = 0; memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(instack,false,sizeof(instack)); while(!st.empty()) { st.pop(); } for(int i=1;i<=n;i++) { if(!dfn[i]) tarjan(i); } } void solve(int n) { find_scc(n); for(int i=1;i<=n;i++) { for(int j=first[i];j!=-1;j=next[j]) { if(sccno[i]!=sccno[edge[j].b]) { out[sccno[i]]++; in[sccno[edge[j].b]]++; } } } vector <int> temp; for(int i=1;i<=n;i++) { if(out[sccno[i]] == 0) temp.push_back(i); } for(int i=0;i<temp.size();i++) { if(i == 0) printf("%d",temp[i]); else printf(" %d",temp[i]); } puts(""); } void init() { memset(first,-1,sizeof(first)); memset(next,-1,sizeof(next)); memset(in,0,sizeof(in)); memset(out,0,sizeof(out)); total = 0; } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif int n,m; int a,b; while(scanf(" %d",&n)!=EOF && n!=0) { scanf(" %d",&m); init(); for(int i=0;i<m;i++) { scanf(" %d %d",&a,&b); addEdge(a,b); } solve(n); } return 0; }第二题:Poj 2186 Popular Cows
题目链接:http://poj.org/problem?id=2186
和上题类似,强连通分量所点后求出度为0的点,但是此题只能有一个出度为0的强联通。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <vector> #include <queue> #include <stack> #include <algorithm> using namespace std; #define Maxn 10005 #define Maxm 50005 struct Edge { int a,b; }edge[Maxm]; int first[Maxn]; int next[Maxm]; int total; int sccno[Maxn]; bool instack[Maxn]; int dfn[Maxn]; int low[Maxn]; int dfs_clock; int scc_cnt; int in[Maxn],out[Maxn]; stack <int> st; void addEdge(int a,int b) { total++; edge[total].a = a;edge[total].b = b; next[total] = first[a]; first[a] = total; } void tarjan(int u) { dfn[u] = low[u] = ++dfs_clock; st.push(u); instack[u] = true; for(int i=first[u];i!=-1;i=next[i]) { int v = edge[i].b; if(!dfn[v]) { tarjan(v); low[u] = min(low[u],low[v]); } else if(instack[v]) { low[u] = min(low[u],dfn[v]); } } if(dfn[u] == low[u]) { scc_cnt++; while(1) { int v = st.top(); st.pop(); instack[v] = false; sccno[v] = scc_cnt; if(u == v) break; } } } void find_scc(int n) { scc_cnt = dfs_clock = 0; memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(instack,false,sizeof(instack)); while(!st.empty()) { st.pop(); } for(int i=1;i<=n;i++) { if(!dfn[i]) tarjan(i); } } int solve(int n) { find_scc(n); for(int i=1;i<=n;i++) { for(int j=first[i];j!=-1;j=next[j]) { if(sccno[i]!=sccno[edge[j].b]) { out[sccno[i]]++; in[sccno[edge[j].b]]++; } } } int ans = 0; int bottomNum = 0; int flag = 0; for(int i=1;i<=n;i++) { if(out[sccno[i]] == 0) { ans ++; if(flag == 0) { bottomNum = sccno[i]; flag = 1; } else { if(bottomNum!=sccno[i]) return 0; } } } return ans; } void init() { memset(first,-1,sizeof(first)); memset(next,-1,sizeof(next)); memset(in,0,sizeof(in)); memset(out,0,sizeof(out)); total = 0; } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif int n,m; int a,b; while(scanf(" %d %d",&n,&m)!=EOF) { init(); for(int i=0;i<m;i++) { scanf(" %d %d",&a,&b); addEdge(a,b); } int ans = solve(n); printf("%d\n", ans); } return 0; }第三题:Poj 1904 King's Quest
题目链接:http://poj.org/problem?id=1904
先把男上向喜欢的女生连线,然后在给出的一组可行解中。女生向对应的男生连线。
求此有向图的强连通分量即可。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <vector> #include <queue> #include <stack> #include <algorithm> using namespace std; #define Maxn 4005 #define Maxm 300005 struct Edge { int a,b; }edge[Maxm]; int first[Maxn]; int next[Maxm]; int total; int sccno[Maxn]; bool instack[Maxn]; int dfn[Maxn]; int low[Maxn]; int dfs_clock; int scc_cnt; stack <int> st; void addEdge(int a,int b) { total++; edge[total].a = a;edge[total].b = b; next[total] = first[a]; first[a] = total; } void tarjan(int u) { dfn[u] = low[u] = ++dfs_clock; st.push(u); instack[u] = true; for(int i=first[u];i!=-1;i=next[i]) { int v = edge[i].b; if(!dfn[v]) { tarjan(v); low[u] = min(low[u],low[v]); } else if(instack[v]) { low[u] = min(low[u],dfn[v]); } } if(dfn[u] == low[u]) { scc_cnt++; while(1) { int v = st.top(); st.pop(); instack[v] = false; sccno[v] = scc_cnt; if(u == v) break; } } } void find_scc(int n) { scc_cnt = dfs_clock = 0; memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(instack,false,sizeof(instack)); while(!st.empty()) { st.pop(); } for(int i=1;i<=n;i++) { if(!dfn[i]) tarjan(i); } } int solve(int n) { find_scc(n); return 0; } void init() { memset(first,-1,sizeof(first)); memset(next,-1,sizeof(next)); total = 0; } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif int n; int num; int b; init(); while(scanf(" %d",&n)!=EOF) { for(int a=1;a<=n;a++) { scanf(" %d",&num); for(int j=0;j<num;j++) { scanf(" %d",&b); addEdge(a,b+n); } } for(int a=1;a<=n;a++) { scanf(" %d",&b); addEdge(b+n,a); } solve(2*n); vector <int> temp; for(int i=1;i<=n;i++) { temp.clear(); for(int j=first[i];j!=-1;j=next[j]) { int v = edge[j].b; if(sccno[i] == sccno[v]) temp.push_back(v-n); } sort(temp.begin(),temp.end()); printf("%d",temp.size()); for(int j=0;j<temp.size();j++) { printf(" %d",temp[j]); } puts(""); } } return 0; }