tarjan

终于来到了差点让我破防的tarjan

争取说明白吧

定义:

1. 桥:指去掉该边,其原本所在的强连通分量变为两部分(即不再是强连通分量)

2. 边双连通分量:即没有无向连通图

3. 强连通分量:即没有桥的有向连通图

 

求无向图的边双连通分量的数量


我们的思路是,从某一节点作为根节点开始dfs遍历图,使用一个$dfn$记录该节点的$dfs$序

再用一个$low$,记录这个点可以往上跑,跑到的最高的点的$dfn$的值

显然,如果$dfn[i] > low[i]$,则说明该节点可以查到自己的祖先,记作种类2

那如果$dfn[i] = low[i]$ ,就说明这个点就是所在连通分量的最上面一个点,记作种类1

我们开一个栈,然后访问到一个点就把这个点压入栈中

对于种类2,必然在栈中位于种类1的前面(因为后遍历到)

所以一旦发现点p为种类1,直接弹栈至p,其中弹出的所有元素都跟p在一个强连通分量里

代码如下:

#include<iostream>
#include<cstdio>
#include<stack>
#define NUM 1010
#define FOR(a,b,c) for( int a = b;a <= c;a++ )
using namespace std;

int n;
int a[NUM];
struct bian{
    int next,to;
};
bian e[NUM];
int head[NUM];
int dfn[NUM],low[NUM];
int id[NUM];
int cnt,scc;

void add( int x,int y ){
    e[++cnt].next = head[x];
    e[cnt].to = y;
    head[x] = cnt;
}
int timess;
stack <int> st;
bool ins[NUM];//判断这个点是不是已经在栈里了
void tarjan( int p ){
    dfn[p] = ++timess;
    low[p] = timess;//更新时间戳
    st.push( p );ins[p] = 1;//将该点压入栈中
    for( int i = head[p];i;i = e[i].next ){
        int to = e[i].to;
        if( !dfn[to] ){ //如果终点没有访问过
            tarjan( to );
            low[p] = min( low[p],low[to] );
        }else if( ins[to] ) //已经在栈里说明之前已经访问过,是之前的点
            low[p] = min( dfn[to],low[p] ); 
    }
    if( dfn[p] == low[p] ){ //以下是一个独立的强连通分量
        ++scc;//分量数++
        int hao;//栈顶点编号
        do{
            hao = st.top();
            st.pop();
            id[hao] = scc;//该点所在的分量编号
        }while( hao != p );//从栈顶到该点是一个连通分量
    }  
}
int m;
int main(){
    
    cin >> n >> m;
    FOR( i,1,m ){ //边数
        int x,y;
        cin >> x >> y;
        add( x,y );//有向边
    }
    
    FOR( i,1,n )
        if( !dfn[i] )
            tarjan( i );

    cout << "\n\n";

    FOR( i,1,n ) //输出每个点所在的连通块编号
        cout << i << " " << id[i] << "\n";
    
    return 0;
}
tarjan求强连通分量

 

边双连通分量

 

 


 

posted @ 2022-10-05 17:32  little_sheep_xiaoen  阅读(15)  评论(0编辑  收藏  举报