tarjan求强联通分量

题目链接

一、前置芝士

强连通分量,指的是在一个有向图中的一部分,这部分里每个点都可以到达其他点。

二、思路

我们现在有一个存节点的栈S,两个数组 \(dfn_i\)\(low_i\)
\(dfn_i\):点\(i\)是第几个被搜索到的。
\(low_i\):所有\(i\)搜到的点的最小\(dfn\)
我们每次从一个点 \(x\) 开始搜索 \(dfs(x)\),搜索到 \(dfs(u)\) 时过程为几个步骤:

  1. 初始化
    因为一个点肯定搜到了自己,所以最开始 \(low_x\)=\(dfn_x\),加入栈中。
  2. 向外拓展
    对于所有的 \(u\rightarrow v\)
    • 如果 \(v\) 没有入栈,那么\(dfs(v)\)\(low_u=\min(low_u,low_v)\)
    • 如果 \(v\) 还在栈内,那么\(low_u=\min(low_u,dfn_v)\)
    • 如果 \(v\) 已经出栈,那么不做任何操作。
  1. 处理强连通分量
    如果做了这么一圈之后\(low_u=dfn_u\),说明他形成了一个强连通分量。我们就需要弹出栈顶直到把 \(u\) 也弹出。因为这题需要,我们还需要记录弹出了多少个,如果 \(>1\) 就算作一个强联通分量。

三、代码

#include<bits/stdc++.h>
using namespace std;
int n,m;
vector<int> v[10005];//存图
bool in[10005];//记录是否入栈
int dfn[10005],low[10005],sum=0,ans=0;//sum是用于求dfn,ans是记录强连通分量个数
stack<int> s;//栈
void dfs(int now){//dfs
    s.push(now);//初始化
    in[now]=1;//标记为入栈
    low[now]=dfn[now]=++sum;
    for(int i=0;i<v[now].size();i++){//找到所有的v
        int to=v[now][i];//to即上文中的v
        if(dfn[to]==0){//==0说明他没有被初始化,也就是没有入栈过
            dfs(to);//入栈
            low[now]=min(low[now],low[to]);
        }
        else if(in[to]){//在栈内
            low[now]=min(low[now],dfn[to]);
        }//已经出栈不用做什么
    }
    if(dfn[now]==low[now]){
        int cnt=1;//cnt记录弹了多少点
        in[now]=0;//不在栈内
        while(s.top()!=now){//只要栈顶不是now
            in[s.top()]=0;//不在栈内
            s.pop();//弹出
            cnt++;//记录
        }
        s.pop();//最后把now弹掉
        if(cnt>1) ans++;//记录ans
    }
}
int main(){
    cin>>n>>m;//输入
    int x,y;
    for(int i=1;i<=m;i++){
        cin>>x>>y;
        v[x].push_back(y);
    }
    for(int i=1;i<=n;i++){//一定要遍历每一个点,图不一定连通
        if(!dfn[i]){//==0,说明没访问过(dfn从1开始)
            dfs(i);
        }
    }
    cout<<ans<<endl;//输出
    return 0;
}
posted @ 2021-12-11 21:57  cyx001  阅读(29)  评论(0编辑  收藏  举报