Tarjan的强联通分量

  求强联通分量有很多种。 《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;
}
时间: 2024-11-07 06:28:31

Tarjan的强联通分量的相关文章

codevs 2822 爱在心中 tarjan(强联通分量)

2822 爱在心中 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Description “每个人都拥有一个梦,即使彼此不相同,能够与你分享,无论失败成功都会感动.爱因为在心中,平凡而不平庸,世界就像迷宫,却又让我们此刻相逢Our Home.” 在爱的国度里有N个人,在他们的心中都有着一个爱的名单,上面记载着他所爱的人(不会出现自爱的情况).爱是具有传递性的,即如果A爱B,B爱C,则A也爱C.如果有这样一部分人,他们彼此都相爱,则他们就超越了一

USACO06JAN The Cow Prom /// tarjan求强联通分量 oj24219

题目大意: n个点 m条边的图 求大小大于1的强联通分量的个数 https://www.cnblogs.com/stxy-ferryman/p/7779347.html tarjan求完强联通分量并染色后 计算一下每种颜色的个数 就是每个强联通块的大小 #include <stdio.h> #include <cstring> #include <algorithm> #include <stack> using namespace std; const i

hihoCoder#1185 : 连通性&#183;三 tarjan求强联通分量 缩点 dfs/拓扑排序求路径和最大值

题目链接: http://hihocoder.com/problemset/problem/1185# 题意: n个点,每个点有一个权值,m条有向边,从1出发,每走到一个点, 就吃掉这个点的草,当没有可以到达的草场或是能够到达的草场都已经被吃光了之后就要返回到1了.求最多可以吃掉多少草. 思路: 提示里面讲的挺好的 如果草场是一个强连通图,那么我们只要走到任意一点,就可以把其他所有的草场都走一遍,并且可以选择任意一个点作为终点.所以把强联通块缩成一个点 因为一个强连通块会被缩成一个点,那么我们可

tarjan求强联通分量 模板

1 void tarjan(int u) 2 { 3 dfn[u]=low[u]=++dfs_clock; 4 stack_push(u); 5 6 for (int c=head[u];c;c=nxt[c]) 7 { 8 int v=to[c]; 9 if (!dfn[v]) 10 { 11 tarjan(v); 12 low[u]=min(low[u],low[v]); 13 } 14 else if (!scc[v]) 15 low[u]=min(low[u],dfn[v]); 16 }

tarjan算法求强联通分量

昨天学到了一个新的算法tarjan算法,感觉最近都没有怎么学习了...(最近有个感悟啊,就是学习一定的通过实践来进步的. 现在才明白为什么高中的时候老师强调一定要刷题,当然刷完题目之后的总结也非常地重要! 这个tarjan算法用来求强联通分量,在网上看了几篇blog,然后做了一个题目,感觉这个算法很nice啊... 如果没有学这个算法, 我肯定会想直接dfs吧orz... dfs看看是不是每个点能到达连通分量的其他点,好像这样非常麻烦啊,还要记录这个点从哪里来的...这样一想,好像直接dfs我做

HDU 1269 迷宫城堡 (强联通分量,Tarjan算法)

Problem Description: 为了训练小希的方向感,Gardon建立了一座大城堡,里面有N个房间(N<=10000)和M条通道(M<=100000),每个通道都是单向的,就是说若称某通道连通了A房间和B房间,只说明可以通过这个通道由A房间到达B房间,但并不说明通过它可以由B房间到达A房间.Gardon需要请你写个程序确认一下是否任意两个房间都是相互连通的,即:对于任意的i和j,至少存在一条路径可以从房间i到房间j,也存在一条路径可以从房间j到房间i. Input: 输入包含多组数据

bzoj 2438: [中山市选2011]杀人游戏 (强联通分量 Tarjan)

Description 一位冷血的杀手潜入 Na-wiat,并假装成平民.警察希望能在 N 个人里面,查出谁是杀手. 警察能够对每一个人进行查证,假如查证的对象是平民,他会告诉警察,他认识的人, 谁是杀手, 谁是平民. 假如查证的对象是杀手, 杀手将会把警察干掉. 现在警察掌握了每一个人认识谁. 每一个人都有可能是杀手,可看作他们是杀手的概率是相同的. 问:根据最优的情况,保证警察自身安全并知道谁是杀手的概率最大是多少? Input 第一行有两个整数 N,M. 接下来有 M 行,每行两个整数 x

Light OJ 1034 - Hit the Light Switches(强联通分量)

题目链接:http://www.lightoj.com/volume_showproblem.php?problem=1034 题目大意:有n盏灯,有m个关系, 关系a,b表示如果a灯开关打开那么b灯也会亮起来, 现在求至少需要打开多少开关使所有灯都亮. 题目思路:先由强联通分量缩点, 得到DAG图, 然后根据DAG图,求出有多少入度为0的点, 即为所求. 代码如下: #include<bits/stdc++.h> using namespace std; const int N = 1000

[BZOJ1051] [HAOI2006] 受欢迎的牛 (强联通分量)

Description 每一头牛的愿望就是变成一头最受欢迎的牛.现在有N头牛,给你M对整数(A,B),表示牛A认为牛B受欢迎. 这 种关系是具有传递性的,如果A认为B受欢迎,B认为C受欢迎,那么牛A也认为牛C受欢迎.你的任务是求出有多少头 牛被所有的牛认为是受欢迎的. Input 第一行两个数N,M. 接下来M行,每行两个数A,B,意思是A认为B是受欢迎的(给出的信息有可能重复,即有可 能出现多个A,B) Output 一个数,即有多少头牛被所有的牛认为是受欢迎的. Sample Input 3