先把参考资料的传送门放一下:
1、优质的B站视频讲解
(里面老师给出了一个很不错的代码模板)
2、tarjan求强连通分量+缩点+割点/割桥(点双/边双)以及一些证明(原理简述)
先不论缩点是怎么做的,
根据第一个B站视频里的模板,
先用线性的邻接表维护有向图,
基本实现一下Tarjan算法。
const int maxn = 1e5 + 10;
//线性邻接表维护有向图
class Node{
public:
int nd,nxt;
Node(){};
Node(int nd_,int nxt_):nd(nd_),nxt(nxt_){};
};
Node G[maxn];
int head[maxn];
int cnt;
void add(int u,int v){
G[cnt] = Node(v,head[u]);
head[u] = cnt++;
}
void init_G(){
memset(head,-1,sizeof(head));
cnt = 0;
}
//下面是Tarjan算法求强连通图
int Low[maxn];
int Dfn[maxn];
int num = 0;//编号变量-赋值不能是0
int out[maxn];//标记被栈弹出过的元素
int Stack[maxn];//模拟栈
int cur = 0;//cur应当始终指向Stack的尾端元素
int ans = 0;
/*我们假设结点的编号从1开始*/
int dfs(int u){
Low[u] = Dfn[u] = ++num;//结点编号
Stack[++cur] = u;//结点入栈
//下面开始遍历u的临界结点
for(int i=head[u];~i;i=G[i].nxt){
int v = G[i].nd;
if(Dfn[v] == 0){//v没有被访问
dfs(v);
Low[u] = min(Low[u],Low[v]);
}else if(!out[v]){//v已经被访问且还在栈中
Low[u] = Low[v];//形成环
}
}
//输出强连通分量
if(Low[u] == Dfn[u]){
int cur_num = 0;
do{
cur_num++;
out[Stack[cur]]=1;
}while(Stack[cur--] != u);
//这里做的处理是为了统计点个数大于1的连通区块的总个数ans
if(cur_num>1)ans++;
}
return 0;
}
int main(){
frein("in.txt");
int n,m;
cin>>n>>m;
init_G();
for(int i=0;i<m;i++){
int a,b;
cin>>a>>b;
add(a,b);
}
//由于图并不一定是弱连通图
//所以这里对每一个没有访问过的结点都进行搜索
//当然,一般这并不会让复杂度达到O(n*n)
for(int i=1;i<=n;i++){
if(!Dfn[i]){
dfs(i);
}
}
cout<<ans<<endl;
return 0;
}
OK