【Tarjan求强连通分量】【模板】

分析

  1. 首先可以采用dfs的方式,对每个点遍历一遍,若其尚未访问,则以它为起点dfs,那么此次dfs中未遍历到的点一定不可能与遍历到的点形成强连通分量,因为强连通分量要求能够互相到达。
  2. 在一次dfs中,每个scc一定存在一个节点是这个scc中其他所有点的祖先节点。证明:否则,这个scc可以划分为若干棵树,这些树之间只能以横叉边相连,而横叉边只能从dfn序大的指向小的,而强连通分量要求相互到达,且其中任意两棵树一定满足其中的一棵树的所有节点的dfn序一定大于另一颗树的所有节点的dfn序,那么这就会形成矛盾。
  3. 我们考虑维护一个栈,保存所有能够到达当前节点x的节点。那么栈中一定包括所有x的祖先节点,还有通过反向边可以到达x的祖先节点的点。定义low[x]为x能通过最多一条非树边到达的dfn序最小的在栈中的节点。那么当回溯时若有dfn[x]==low[x]则可判定从栈顶到x的所有节点构成一个scc
  4. 访问到一个新节点x时,把x压进栈,dfn[x]=low[x]=++tot.
  5. 对于x连向的每个节点y:若y尚未访问过,则(x,y)是树枝边,先dfs(y),用lowy更新lowx(这里其实有两种情况,见Tip);若y被访问过,且y在栈中,用dfny更新lowx
  6. 回溯时若有若有dfn[x]==low[x],则可一直弹栈直到x出栈,弹出的所有节点构成一个scc
  7. 缩点时枚举每条边,若两端点分属不同scc,则加入新DAG中。
    Tip:若lowy可以到达x,那么满足条件,可以更新lowx。如果lowy不可以到达x,那么lowy一定等于y,大于dfnx,一定不会对lowx造成影响。

Code(校园网络,虽然里面的记录的scc没什么用,但在别的题中也许会用到)

#include<bits/stdc++.h>
using namespace std;
//#define int long long
inline int read()
{
	register int x=0,w=1;
	register char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if(ch=='-') {w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
	return x*w;
}
inline void write(int x)
{
	if(x<0) putchar('-'),x=~(x-1);
	if(x>9) write(x/10);
	putchar('0'+x%10);
}
const int N=1e4+100;
int n,ins[N],stk[N],top,tot,c[N],dfn[N],low[N],cnt,ind[N],od[N];
vector<int>v[N],scc[N];
void tarjan(int x)
{
	dfn[x]=low[x]=++tot;stk[++top]=x;ins[x]=1;
	for(int i=0;i<v[x].size();++i)
	{
		int y=v[x][i];
		if(!dfn[y]){
			tarjan(y); low[x]=min(low[x],low[y]);
		}
		else if(ins[y]) low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x]){
		cnt++;int z;
		do
		{
			z=stk[top--];
			scc[cnt].push_back(z);c[z]=cnt;ins[z]=0;
		}while(z!=x);
	}
}
signed main()
{
    n=read();
    for(int i=1;i<=n;++i)
    {
    	int x=read();
    	while(x) v[i].push_back(x),x=read();
	}
	for(int i=1;i<=n;++i){
		if(!dfn[i]) tarjan(i);
	}
	for(int i=1;i<=n;++i)
	{
		for(int j=0;j<v[i].size();++j)
		{
			int y=v[i][j];
			if(c[i]==c[y]) continue;
			od[c[i]]++;ind[c[y]]++;
		}
	}
	int a=0,b=0;
	for(int i=1;i<=cnt;++i) {
		if(ind[i]==0) a++;
		if(od[i]==0) b++;
	}
	write(a);puts("");
	if(cnt==1) {
		puts("0");return 0;
	}
	write(max(a,b));
	
	return 0;
}
posted @ 2022-05-09 20:35  glq_C  阅读(44)  评论(0)    收藏  举报