刚学tarjan,写篇题解加深下印象。

tarjan算法详解

给定一张有向图,求它的所有强连通分量。

每次选择一个未遍历的点开始dfs。

定义dfn[i]表示i被遍历到的时间,low[i]表示从i出发,只经过至多一条dfn变小的边,能走到的dfn最小的点的dfn的值。(可能有些绕,看不懂建议多看几遍)

维护一个栈,dfs到一个点时将它加入栈内。

如果一个点的dfn=low,那么不停弹出栈顶元素直到这个点被弹出,被弹出的所有点就构成了一个强连通分量。

时间复杂度O(m),m是边数


思路:先用tarjan缩点,然后统计入度为0的点的个数(缩点指将每一个强连通分量缩成一个点)

这里有一个小技巧,可以不用在缩点后重新建图,直接统计入度即可

code:

#include<iostream>
#include<vector>
using namespace std;
#define N 100005
int dfn[N],low[N],tim=1,stack[N],top=0,belong[N],pn=0,du[N];
bool in[N];
vector<int> a[N];
void tarjan(int u){
	low[u]=tim;
	dfn[u]=tim++;
	stack[top++]=u;
	in[u]=true;//先初始化dfn值和low值,并压入栈
	int siz=a[u].size(),v;
	for(int i=0;i<siz;i++){
		v=a[u][i];
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(in[v]) 
			low[u]=min(low[u],low[v]);//递归并更新low值
	}
	if(dfn[u]==low[u]){
		pn++;
		while(stack[top]!=u){
			top--;
			belong[stack[top]]=pn;
			in[stack[top]]=false;
		}
	}//将强连通分量中的点打上标记
}
int main()
{
	int n,m,x,y,ans=0;
	cin>>n>>m;
	for(int i=0;i<m;i++){
		cin>>x>>y;
		if(x!=y)
			a[x].push_back(y);
	}
	for(int i=1;i<=n;i++)
		if(!dfn[i])
			tarjan(i);//如果没有遍历过,那就遍历它
	for(int i=1;i<=n;i++)
		for(int j=0;j<a[i].size();j++)
			if(belong[i]!=belong[a[i][j]])
				du[belong[a[i][j]]]++;//统计入度
	for(int i=1;i<=pn;i++)
		ans+=(du[i]==0);//统计答案
	cout<<ans;
	return 0;
}