hdu1269(强连通模版题)

迷宫城堡

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 4794    Accepted Submission(s): 2106


Problem Description
为了训练小希的方向感,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
 

Author
Gardon
 

Source
HDU 2006-4 Programming Contest
 

Recommend
lxj

题目大意:判断一个图是否是强连通。

题目分析:强连通模版直接上。

求强连通一般有3个算法。

1:Kosaraju_Algorithm

主要方法是先在原图中进行一次dfs,求出每个点离开时间。

然后在反图上根据第一次求出所有顶点离开时间的降序第二次dfs,那么每一次dfs遍历形成的子树就是一个强连通分量。

详情请见代码:

/*Kosaraju_Algorithm*/
#include <iostream>
#include<cstdio>
using namespace std;
const int N = 10005;
int headg[N],headgt[N];
struct edge
{
    int to,next;
}g[N<<4],gt[N<<4];
bool flag[N];
int sign[N];//离开时间
int scc[N];//记录连通分量
int n,m,nb;
void dfs1(int cur,int &t)
{
    flag[cur] = 1;
    for(int i = headg[cur];i != -1;i = g[i].next)
    {
        if(!flag[g[i].to])
            dfs1(g[i].to,t);
    }
    sign[t ++] = cur;
}

void dfs2(int cur,int sig)
{
    scc[cur] = sig;
    flag[cur] = 1;
    for(int i = headgt[cur];i != -1;i = gt[i].next)
    {
        if(!flag[gt[i].to])
            dfs2(gt[i].to,sig);
    }
}

int Kosaraju()
{
    int i,sig;
    nb = 1;
    memset(flag,0,sizeof(flag));
    for(i = 1;i <= n;i ++)
    {
        if(!flag[i])
            dfs1(i,nb);
    }
    memset(flag,0,sizeof(flag));
    for(sig = 0,i = n;i >= 1;i --)
    {
        if(!flag[sign[i]])
            dfs2(sign[i],++sig);
    }
    return sig;
}

int main()
{
    int i,j,a,b;
    while(scanf("%d%d",&n,&m),(m + n))
    {
        memset(headg,-1,sizeof(headg));
        memset(headgt,-1,sizeof(headgt));
        for(i = 1;i <= m;i ++)//建原图和反图
        {
            scanf("%d%d",&a,&b);
            g[i].to = b;
            g[i].next = headg[a];
            headg[a] = i;
            gt[i].to = a;
            gt[i].next = headgt[b];
            headgt[b] = i;
        }
        if(Kosaraju() == 1)
            printf("Yes\n");
        else
            printf("No\n");
    }
    return 0;
}
//15MS	2156K
2:Tarjan_Algorithm

这是一个比较经典的算法,利用标号,直接省掉了Kosaraju_Algorithm的一次dfs,当然也就省掉了一张图。主要思路是直接求出每一个强连通分量的树根,具体做法就是用2个标号数组,index[i]表示访问点i的时间,mlik[i]表示i所在的强连通分量的最先访问时间。当某个点的index值和mlik值相等时说明找到了一个强连通分量的树根,用一个栈维护节点,访问的节点全部入栈,当找到某个树根时,弹出根节点到栈顶的所有元素,这些元素就属于当前节点的一个强连通分量。具体过程可以类比并查集,不断更新的mlik值就相当于找根,同一个强连通的节点的mlik都指向最先访问到这个强连通分量的节点,是为树根。

详情请见代码:

/*Tarjan_Algorithm*/
#include<cstdio>
#include<cstring>
const int N = 10005;
int head[N];
struct edge
{
    int to,next;
}g[N<<4];
int flag[N];
int scc[N];
int stack[N];
int mlik[N];
int index[N];
int n,m;

void dfs(int cur,int &t,int &sig)
{
    flag[cur] = 1;
    stack[++stack[0]] = cur;
    index[cur] = mlik[cur] = t ++;
    for(int i = head[cur];i != -1;i = g[i].next)
    {
        if(flag[g[i].to] == 0)
        {
            dfs(g[i].to,t,sig);
            mlik[cur] = mlik[cur] > mlik[g[i].to]?mlik[g[i].to]:mlik[cur];
        }
        if(flag[g[i].to] == 1)
            mlik[cur] = mlik[cur] > index[g[i].to]?index[g[i].to]:mlik[cur];
    }
    if(index[cur] == mlik[cur])
    {
        ++sig;
        do
        {
            scc[stack[stack[0]]] = sig;
            flag[stack[stack[0]]] = 2;
        }while(stack[stack[0] --] != cur);
    }
}

int Tarjan()
{
    int i,sig,t;
    sig = 0;
    t = 1;
    stack[0] = 0;
    memset(flag,0,sizeof(flag));
    for(i = 1;i <= n;i ++)
    {
        if(flag[i] == 0)
            dfs(i,t,sig);
    }
    return sig;
}

int main()
{
    int i,j,a,b;
    while(scanf("%d%d",&n,&m),(n + m))
    {
        memset(head,-1,sizeof(head));
        for(i = 1;i <= m;i ++)
        {
            scanf("%d%d",&a,&b);
            g[i].to = b;
            g[i].next = head[a];
            head[a] = i;
        }
        if(Tarjan() == 1)
            printf("Yes\n");
        else
            printf("No\n");
    }
    return 0;
}
//31MS	1508K

3:Gabow_Algorithm

此方法是第二种方法的改进,这里用第二个栈代替了方法二中的2个标号数组,辅助求强连通分量的根。改进了方法二中的频繁更新标号数组的缺点。

详情请见代码:

/*Gabow_Algorithm*/
#include<cstdio>
#include<cstring>
const int N = 10005;
int head[N];
int scc[N];
int stack1[N];
int stack2[N];
int in[N];
struct edge
{
    int to,next;
}g[N<<4];
int n,m;

void dfs(int cur,int &sig,int &ans)
{
    in[cur] = ++ sig;
    stack1[++stack1[0]] = cur;
    stack2[++stack2[0]] = cur;
    for(int i = head[cur];i != -1;i = g[i].next)
    {
        if(in[g[i].to] == 0)
            dfs(g[i].to,sig,ans);
        else
            if(scc[g[i].to] == 0)
            {
                while(in[stack2[stack2[0]]] > in[g[i].to])
                    stack2[0] --;
            }
    }
    if(stack2[stack2[0]] == cur)
    {
        stack2[0] --;
        ans ++;
        do
        {
            scc[stack1[stack1[0]]] = ans;
        }while(stack1[stack1[0]--] != cur);
    }
}

int Gabow()
{
    int i,sig,ret;
    memset(in,0,sizeof(in));
    memset(scc,0,sizeof(scc));
    stack1[0] = stack2[0] = sig = ret = 0;
    for(i = 1;i <= n;i ++)
        if(!in[i])
            dfs(i,sig,ret);
    return ret;
}

int nextint()
{
    char c;
    int ret;
    while((c = getchar()) > '9' || c < '0')
        ;
    ret = c - '0';
    while((c = getchar()) >= '0' && c <= '9')
        ret = ret * 10 + c - '0';
    return ret;
}

int main()
{
    int i,j,a,b;
    while(scanf("%d%d",&n,&m),(n + m))
    {
        memset(head,-1,sizeof(head));
        for(i = 1;i <= m;i ++)
        {
            //scanf("%d%d",&a,&b);
            a = nextint();
            b = nextint();
            g[i].to = b;
            g[i].next = head[a];
            head[a] = i;
        }
        if(Gabow() == 1)
            printf("Yes\n");
        else
            printf("No\n");
    }
    return 0;
}
//0MS	1400K

三种方法的比较
Kosaraju_Algorithm理论复杂度是最高的,空间复杂度也很高,因为要建两张图。但是求出的强连通分量隐藏了一个比较好的性质:是一个拓扑序列。

Tarjan_Algorithm比较经典,实现较为简单,空间复杂度较低。

Gabow_Algorithm是Tarjan_Algorithm的改进,时空复杂度是最低的。速度也比较快。

3种算法时间复杂度都是O(v+e)的,Kosaraju_Algorithm常数稍大,空间开销也大,但是有拓扑性质。后2个比较高效,但是不具有拓扑性质。具体看题目情况,合理选择适当算法。



你可能感兴趣的:(图论,强连通)