tarjan求强联通分量
一、前置芝士
强连通分量,指的是在一个有向图中的一部分,这部分里每个点都可以到达其他点。
二、思路
我们现在有一个存节点的栈S,两个数组 \(dfn_i\) 和 \(low_i\)。
\(dfn_i\):点\(i\)是第几个被搜索到的。
\(low_i\):所有\(i\)搜到的点的最小\(dfn\)。
我们每次从一个点 \(x\) 开始搜索 \(dfs(x)\),搜索到 \(dfs(u)\) 时过程为几个步骤:
- 初始化
因为一个点肯定搜到了自己,所以最开始 \(low_x\)=\(dfn_x\),加入栈中。 - 向外拓展
对于所有的 \(u\rightarrow v\):
-
- 如果 \(v\) 没有入栈,那么\(dfs(v)\),\(low_u=\min(low_u,low_v)\);
- 如果 \(v\) 还在栈内,那么\(low_u=\min(low_u,dfn_v)\);
- 如果 \(v\) 已经出栈,那么不做任何操作。
- 处理强连通分量
如果做了这么一圈之后\(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;
}