【强联通图 | 强联通分量】HDU 1269 迷宫城堡 【Kosaraju或Tarjan算法】
为了训练小希的方向感,Gardon建立了一座大城堡,里面有N个房间(N<=10000)和M条通道(M<=100000),每个通道都是单向的,就是说若称某通道连通了A房间和B房间,只说明可以通过这个通道由A房间到达B房间,但并不说明通过它可以由B房间到达A房间。Gardon需要请你写个程序确认一下是否任意两个房间都是相互连通的,即:对于任意的i和j,至少存在一条路径可以从房间i到房间j,也存在一条路径可以从房间j到房间i。
Input
输入包含多组数据,输入的第一行有两个数:N和M,接下来的M行每行有两个数a和b,表示了一条通道可以从A房间来到B房间。文件最后以两个0结束。
Output
对于输入的每组数据,如果任意两个房间都是相互连接的,输出"Yes",否则输出"No"。
Sample Input
3 3 1 2 2 3 3 1 3 3 1 2 2 3 3 2 0 0
Sample Output
Yes No
题目大意:一个有向图,有n个点和m条边。判断整个图是否强连通,如果是,输出Yes,否则输出No。
题目可以用Kosaraju算法和Tarjan算法。
详解来自于:《算法竞赛 入门到进阶》
Kosaraju算法:
Kosaraju算法用到了“反图”的技术,基于下面两个原理:
(1)一个有向图G,把G所有的边反向,建立反图rG,反图rG不会改变原图G的强连通性。也就是说,图G的SCC数量与rG的SCC(强联通分量)数量相同。
(2)对原图G和反图rG各做一次DFS,可以确定SCC数量。
代码:
#pragma comment(linker, "/STACK:1024000000,1024000000") #pragma GCC optimize(2) #include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<queue> #include<set> #include<cmath> #include<string> #include<map> #include<vector> #include<ctime> #include<stack> using namespace std; #define mm(a,b) memset(a,b,sizeof(a)) typedef long long ll; const long long mod = 1e9+7; const int maxn = 1e4+10; const int inf = 0x3f3f3f3f; vector<int>G[maxn],rG[maxn]; vector<int>S;//存第一次dfs1的结果:标记点的先后顺序 int vis[maxn],sccno[maxn],cnt;//cnt为连通分量的个数 void dfs1(int u) { if(vis[u]) return; vis[u]=1; for(int i=0;i<G[u].size();i++) dfs1(G[u][i]); S.push_back(u);//标记点的先后顺序,标记大的放在S的后面 } void dfs2(int u) { if(sccno[u]) return; sccno[u]=cnt; for(int i=0;i<rG[u].size();i++) dfs2(rG[u][i]); } void Kosaraju(int n) { cnt=0; S.clear(); mm(sccno,0); mm(vis,0); for(int i=1;i<=n;i++) dfs1(i); //点的编号:1~n递归所有点 for(int i=n-1;i>=0;i--) if(!sccno[S[i]]) { cnt++; dfs2(S[i]); } } int main() { int n,m,u,v; while(scanf("%d %d",&n,&m),n||m) { for(int i=0;i<n;i++) { G[i].clear(); rG[i].clear(); } for(int i=0;i<m;i++) { scanf("%d %d",&u,&v); G[u].push_back(v); rG[v].push_back(u); } Kosaraju(n); if(cnt==1) printf("Yes\n"); else printf("No\n"); } return 0; }
Tarjan算法
上面的Kosaraju算法,其做法是从图中一个个地把SCC“挖”出来。Tarjan算法能在DFS中把所有点都按SCC分开。
1 #pragma comment(linker, "/STACK:1024000000,1024000000") 2 #pragma GCC optimize(2) 3 #include<iostream> 4 #include<algorithm> 5 #include<cstdio> 6 #include<cstring> 7 #include<queue> 8 #include<set> 9 #include<cmath> 10 #include<string> 11 #include<map> 12 #include<vector> 13 #include<ctime> 14 #include<stack> 15 using namespace std; 16 #define mm(a,b) memset(a,b,sizeof(a)) 17 typedef long long ll; 18 const long long mod = 1e9+7; 19 const int maxn = 1e4+10; 20 const int inf = 0x3f3f3f3f; 21 int cnt; //强连通分量的个数 22 int low[maxn],num[maxn],dfn; 23 int sccno[maxn]; 24 stack<int>st; 25 vector<int>G[maxn]; 26 27 void dfs(int u) 28 { 29 st.push(u); 30 low[u]=num[u]=++dfn; 31 for(int i=0;i<G[u].size();i++) 32 { 33 int v=G[u][i]; 34 if(!num[v]) //未访问过的点,继续DFS 35 { 36 dfs(v); //DFS的最底层,是最后一个SCC 37 low[u]=min(low[v],low[u]); 38 } 39 else if(!sccno[v]) //处理回退边 40 low[u]=min(low[u],num[v]); 41 } 42 if(low[u]==num[u]) //栈底的点是SCC的祖先,它的low=num 43 { 44 cnt++; 45 while(1) 46 { 47 int v=st.top(); //v弹出栈 48 st.pop(); 49 sccno[v]=cnt; 50 if(u==v) break; //栈底的点是SCC的祖先 51 } 52 } 53 } 54 55 void Tarjan(int n) 56 { 57 cnt=dfn=0; 58 mm(sccno,0); 59 mm(num,0); 60 mm(low,0); 61 for(int i=1;i<=n;i++) 62 if(!num[i]) 63 dfs(i); 64 } 65 66 int main() 67 { 68 int n,m,u,v; 69 while(scanf("%d %d",&n,&m),n||m) 70 { 71 for(int i=1;i<=n;i++) G[i].clear(); 72 for(int i=0;i<m;i++) 73 { 74 scanf("%d %d",&u,&v); 75 G[u].push_back(v); 76 } 77 Tarjan(n); 78 if(cnt==1) printf("Yes\n"); 79 else printf("No\n"); 80 } 81 return 0; 82 }