Tarjan——强连通分量

是的你没有看错,又是Tarjan(说过他发明了很多算法),那么什么是Tarjan

首先看看度娘的解释:

有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。

是不是没有看懂,我也是

首先了解几个概念:强连通,强连通图,强连通分量

强连通:在一个有向图G中,两个点a,b,a可以走到b,b可以走到a,我们就说(a,b)强连通

强连通图:在一个有向图G中,任意两个点都是强连通

强连通分量:在一个有向图G中,有一个子图,它任意两个点都是强连通,我们就说这个子图为强连通分量,特别的,一个点也是一个强连通分量

如图所示:

显然可得:1,2,3,5 构成了一个强连通分量(一个点也是)

下面进入正题,厉害的Tarjan发明的厉害的Tarjan

首先引进一个概念(以前应该都学过),时间戳,用数组dfn表示(应该不用讲吧),也就是搜索这个图的顺序(最后还是讲了)

每个节点的时间戳不同

再是一个low数组,low[x]表示以x为根的子树中,每个节点中连接的点的时间戳的最小值(可能有点难懂,但这个非常重要,是核心思想,等一下的模拟过程会详细讲述)

low的初值:low[x]=dfn[x]

那么如何储存强连通分量呢,可以用栈(学c++的有福利啦,我们有STL)

可惜我不会用,(手写栈挺好的),每次遍历到一个新节点,就把它放进栈,如果这个点有出度,就继续往下找,直到不能再找

每一次回来都要更新low值,当然是取小的那个,如果发现low[x]=dfn[x]那么它的子节点中肯定有一个连上来,既然可以过去又可以回来,很明显是一个强连通分量

那么这个x就是这个强连通分量的根节点,那么栈中间,比这个x晚进来的点就是x的子节点,那么这些点全部出栈,就组成了一个强连通分量

到这来就完了,但是好像还是理解透彻(反正我是这样)

那么就模拟一下

还是这张图5——>4

low[1]=dfn[1]=1,1入栈

low[2]=dfn[2]=2,2入栈

low[3]=dfn[3]=3,3入栈

low[5]=dfn[5]=4,4入栈

然后发现5连接着1,已经寻找过得了,那么就看看,谁才是真正的祖先

low[1]=1,low[5]=4,好吧,5输了,所以1是5的根节点,low[5]=min(low[5],low[1])=1

继续发现还有4,low[4]=dfn[4]=5,4入栈

但是4已经没有了出度,往回退

发现low[4]=dfn[4]那么4就是一个强连通分量的根节点(其实也就它一个),4退栈

继续往回退:low[5]=min(low[5],low[4])=1;

继续:一直到1,low[3]=min(low[3],low[5])=1;low[2]=min(low[2],low[3])=1;

low[1]=min(low[1],low[2])=1;

发现此时low[1]=dfn[1],所以1也是一个强连通分量的根,此时发现栈里还有1,2,3,5

所以这个强连通分量为1,2,3,5

1还有一个出度:4

寻找4,low[4]=dfn[4]=6,发现没有出度

low[4]=dfn[4],所以4是一个强连通分量的根节点(还是只有他一个),退栈

往回退,low[1]=min(low[1],dfn[4])=1;

这样就完了吗?

万一还有图没有遍历到呢

所以要加一个语句:

for(int i=1;i<=number;i++)
       if(!dfn[i])Tarjan(i);

以上的模拟过程用程序表示就是:

void Tarjan(int x){
    dfn[x]=low[x]=++deep;
    q[++hd]=x;res[x]=1;
    for(int i=head[x];i;i=next[i]){
        int y=ver[i];
        if(!dfn[y]){
            Tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if(res[y])low[x]=min(low[x],dfn[y]);
    }
    if(low[x]==dfn[x]){
        int sum=0;
        do{
            res[q[hd]]=0;hd--;sum++;
        }while(x!=q[hd+1]);
        if(sum>1)ans++;
    }

}

来一个模板题:洛谷

P2863 [USACO06JAN]牛的舞会The Cow Prom

大意是求强连通分量数量,不过注意的是,每一个点不能算,所以要加一个特判

以下是AC代码:

#include<bits/stdc++.h>
using namespace std;

const int N=50002;
int number,m,ans=0;
int tot,next[N],ver[N],head[N];
int dfn[N],low[N],deep,res[N],q[N],hd;

int read(){
    int s=0,w=1;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘)w=(ch==‘-‘)?-1:1,ch=getchar();
    while(ch>=‘0‘&&ch<=‘9‘)s=s*10+ch-‘0‘,ch=getchar();
    return s*w;
}

void add(int x,int y){
    ver[++tot]=y;next[tot]=head[x];head[x]=tot;
}

void Tarjan(int x){
    dfn[x]=low[x]=++deep;
    q[++hd]=x;res[x]=1;
    for(int i=head[x];i;i=next[i]){
        int y=ver[i];
        if(!dfn[y]){
            Tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if(res[y])low[x]=min(low[x],dfn[y]);
    }
    if(low[x]==dfn[x]){
        int sum=0;
        do{
            res[q[hd]]=0;hd--;sum++;
        }while(x!=q[hd+1]);
        if(sum>1)ans++;
    }

}

int main(){
    number=read();m=read();
    for(int i=1;i<=m;i++){
        int x=read(),y=read();
        add(x,y);
    }
    for(int i=1;i<=number;i++)
       if(!dfn[i])Tarjan(i);
    cout<<ans;
    return 0;
}

原文地址:https://www.cnblogs.com/GMSD/p/11320154.html

时间: 2024-11-11 20:21:12

Tarjan——强连通分量的相关文章

hdu1269 Tarjan强连通分量 模板(转)

#include<stdio.h> #include<iostream> #include<vector> using namespace std; const int maxn=10010; vector<int>g[maxn]; int Bcnt; int Top; int Index; int low[maxn],dfn[maxn]; int belong[maxn],stack[maxn]; int instack[maxn]; void Init_

tarjan 强连通分量

一.强连通分量定义 有向图强连通分量在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(stronglyconnected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.有向图的极大强连通子图,称为强连通分量(stronglyconnectedcomponents)SCC. 以上是摘自百科的一段对有向图强连通图分量的形式化定义.其实不难理解,举个例子 如上图,{a,b,c,d}为一个强连通分量,{e}

算法模板——Tarjan强连通分量

功能:输入一个N个点,M条单向边的有向图,求出此图全部的强连通分量 原理:tarjan算法(百度百科传送门),大致思想是时间戳与最近可追溯点 这个玩意不仅仅是求强连通分量那么简单,而且对于一个有环的有向图可以有效的进行缩点(每个强连通分量缩成一个点),构成一个新的拓扑图(如BZOJ上Apio2009的那个ATM)(PS:注意考虑有些图中不能通过任意一个单独的点到达全部节点,所以不要以为直接tarjan(1)就了事了,还要来个for循环,不过实际上复杂度还是O(M),因为遍历过程中事实上每个边还是

POJ1236 (Network of Schools,Tarjan,强连通分量)

转载自:http://www.tuicool.com/articles/EnMFFja 原创:kuangbin 题意:  一个有向图,求: 1) 至少要选几个顶点,才能做到从这些顶点出发,可以到达全部顶点 2) 至少要加多少条边,才能使得从任何一个顶点出发,都能到达全部顶点,即成为一个强连通分量 思路: 求完强连通分量后,缩点,计算每个点的入度,出度. 第一问的答案就是入度为零的点的个数, 第二问就是max(n,m)  入度为零的个数为n, 出度为零的个数为m 思路详解: 1. 求出所有强连通分

tarjan——强连通分量+缩点

tarjan陪伴强联通分量 生成树完成后思路才闪光 欧拉跑过的七桥古塘 让你 心驰神往"----<膜你抄>   自从听完这首歌,我就对tarjan开始心驰神往了,不过由于之前水平不足,一直没有时间学习.这两天好不容易学会了,写篇博客,也算记录一下.   一.tarjan求强连通分量 1.什么是强连通分量? 引用来自度娘的一句话: "有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则

tarjan[强连通分量][求割边割点][缩点]

强连通分量: 1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int maxn=100000+15; 5 struct Edge { 6 int x,y,next; 7 Edge(int x=0,int y=0,int next=0): 8 x(x),y(y),next(next) {} 9 } edge[maxn]; 10 int sumedge,head[maxn]; 11 int n,m; 12 int ins(in

tarjan强连通图分量

[有向图强连通分量] 有向图强连通分量的Tarjan算法 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components). 下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达.{5},{6}也分别是两个强连通分量. [Tarjan算法] Tarjan算法是基于对图深

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 Input3 31 22 12

【学习整理】Tarjan:强连通分量+割点+割边

Tarjan求强连通分量 在一个有向图中,如果某两点间都有互相到达的路径,那么称中两个点强联通,如果任意两点都强联通,那么称这个图为强联通图:一个有向图的极大强联通子图称为强联通分量.   算法可以在 的时间内求出一个图的所有强联通分量. 表示进入结点 的时间 表示从 所能追溯到的栈中点的最早时间 如果某个点 已经在栈中则更新  否则对 进行回溯,并在回溯后更新  #include<iostream> #include<cstdlib> #include<cstdio>