tarjan求强连通分量及缩点

蒟蒻求强连通分量及缩点

一、概念
什么是连通、强连通、弱连通?
这里有比较好理解的概念

  • 连通:无向图中,从任意点i可以到达任意点j;
  • 强连通:有向图中,从任意点i可以到达任意点j;
  • 弱联通:把有向图看成无向图,从任意点i可以到达任意点j;
    在有向图中成为强连通分量的条件较为苛刻
    所以引入强连通分量这个概念
  • 强连通分量:局部区域强连通
    {下图为弱连通图}

在这里插入图片描述
二、方法
(所涉及的内容皆围绕CF999E)
我们在计算强连通分量的时候会用到的工具

  • vector邻接表(存图)
  • 栈 st(用来存放属于同一个强连通分量的节点)
  • time (节点被访问的顺序)
  • dfn数组 (存储节点被访问的顺序)
  • low数组 (可以到达的最早的时间点)
  • bool 型的instake数组 (标记节点是否在栈里)
    每一个节点都有两个属性,表示为(a,b)
    a为该节点被真正访问到的顺序,b为可以最快访问到该节点的时间戳
    (P.S. a即为dfn,b即为low)
    对于下面这个有5个节点的单向图
    在遍历图的时候,先将(a,b)赋值为相同的数值
    再搜索该节点所能到达的其他节点
    将其时间戳修改为最小值
    以下图为例
    在这里插入图片描述
    整体思路为先通过遍历赋予节点时间戳
    之后通过dfs回溯维护最小时间戳

    更多细节:
    a获得属性(1.1)
    搜索到节点b可以连通且没有被访问过
    b获得属性(2,2)
    搜索到节点c可以连通且没有被访问过
    c获得属性 (3,3)
    继续搜索到d (4,4)
    继续搜索到b,发现已经被访问过,但是b节点在栈里
    所以将d节点的low与b节点的low比较,取最小的那一个
    故d点属性变为(4,2)
    接着搜索到e,e获得属性(5,5)
    图遍历完之后要检查栈
    栈顶(5,5)的两属性值相等,说明e节点无法到达其他时间戳靠前的节点
    说明e是一个强连通分量的节点
    将e弹出栈
    之后回溯到d(4,2)
    维护时间戳 d(4,2)
    两属性不相等,跳过
    回溯到c(3,3)
    维护时间戳c(3,2)
    两属性不相等,跳过
    回溯到b(2,2)
    维护(2,2)
    两属性值相等
    从栈顶一直弹出到b
    说明bcd为一个强连通分量
    接着回溯到a(1,1)
    两属性值相等,弹出a
    结束
    结果为 (a),(b,c,d),(e)三个强连通分量

在这里插入图片描述
三、缩点
前面已经提到强连通分量的定义,即任意i点可以到达任意j点
那么要是在强连通分量中再添加路径就没有必要
所以就可以把同属于一个强连通分量的节点看为同一个节点
构建为一个新的有向图
对于新的节点,我们需要

  • 了解这样一个关于有向图的知识:
    在有向图中,度又分为入度和出度。
    入度 (in-degree) :以某顶点为弧头,终止于该顶点的弧的数目称为该顶点的入度
    出度 (out-degree) :以某顶点为弧尾,起始于该顶点的弧的数目称为该顶点的出度
  • bel数组(用以标记该节点属于第几个强连通分量)
  • in数组 (统计节点的入度)
  • cnt(记录当前是第几个强连通分量)
    对于新有向图,统计每一个节点的入度,和为该强连通分量(即新节点的入度)
    对于CF的这道题来说,可以这样操作
for( int i = 1 ; i <= n ; i ++ )
        for( int j = 0 ; j < ve[ i ].size( ) ; j ++ )
        {
            int u = bel[ i ], v = bel[ ve[ i ][ j ] ];
            if( u != v ) in[ v ] ++;///如果可以到达的节点不属于一个强连通分量,则入度++;
        }
        ///有关全局变量已在前文明示

因为题目中询问的从s结点出发能够遍历全部节点
那么只需分析除s所在新节点外的新节点的入度
(若与s在同一新节点,则s一定可以到达)
若入度为0,则需要建一条新的有向边
最后附上该题AC代码

#include<stdio.h>
#include<stack>
#include<algorithm>
#include<vector>
using namespace std;
vector<int> ve[5005];
stack<int> st;
int tim=1;///时间戳
int dfn[5005];///真实的被访问的顺序
int low[5005];///可以到达的最早的时间点
int bel[5005];
int in[5005];
bool instack[5005];///记录在栈里的节点

int cnt=0;
void dfs(int node)
{
    st.push(node);
    dfn[node]=tim;
    low[node]=tim++;
    instack[node]=1;
    int siz=ve[node].size();
    for(int i=0; i<siz; i++)
    {
        if(!dfn[ve[node][i]])
        {
            dfs(ve[node][i]);
            low[node]=min(low[node],low[ve[node][i]]);
        }
        else if(instack[ve[node][i]]/*y in stack*/)
        {
            low[node]=min(low[node],low[ve[node][i]]);
        }
    }
    if(dfn[node]==low[node])
    {
        ++cnt;
        while(st.top()!=node)
        {
            instack[st.top()]=0;
            bel[st.top()]=cnt;
            st.pop();
        }
        instack[st.top()]=0;
        bel[st.top()]=cnt;
        st.pop();
    }
}

int main()
{
    int n,m,s;
    ///n:节点数 m:道路数 s:出发点
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1; i<=m; i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        ve[a].push_back(b);
    }///存单向图
    for(int i=1; i<=n; i++)
    {
        if(dfn[i]==0)
            dfs(i);
    }
    
    for( int i = 1 ; i <= n ; i ++ )
        for( int j = 0 ; j < ve[ i ].size( ) ; j ++ )
        {
            int u = bel[ i ], v = bel[ ve[ i ][ j ] ];
            if( u != v ) in[ v ] ++;
        }
    int Ans = 0;
    for( int i = 1 ; i <= cnt ; i ++ )
        if( !in[ i ] && i != bel[ s ] ) Ans ++;
    printf("%d\n",Ans);
    return 0;
}
/*
5 5 1
1 2
2 3
3 4
4 2
4 5
///上图测试数据
*/

不定时更新~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值