【强联通图 | 强联通分量】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 }

 

 
posted @ 2019-08-26 10:37  Tangent_1231  阅读(246)  评论(0编辑  收藏  举报