tarjan 求强连通分量学习笔记

下面全是我自己乱写的,如果理解有误非常正常,可以骂我。

定义

在有向图中,若 \(u\)\(v\) 能够相互到达,则称其强联通。

强连通图,即所有点都是强连通的有向图。

强连通子图,即所有点都是强连通的一个子图。

强连通分量(scc),即极大的强连通子图。

由定义不难发现每个点只在一个强连通分量中,反证易证。

tarjan

把一个无向图的 dfs 树拉出来,此时祖先可到达子孙。

![]( https://cdn.luogu.com.cn/upload/image_hosting/crthitfd.png?x-oss-process=image/resize ,m_lfit,h_170,w_225)

我们考虑在遇到每个强连通分量中在 dfs 树上深度最小的点时把这个强连通分量记录下来。

考虑点 \(u\) 在什么时候是一个强连通分量深度最小的点,显然是它和其任意一个祖先都不联通。

\(u\) 何其祖先一个都不连通,则把 \(u\) 这颗子树作为一个 scc。

但是这样也不一定对,\(u\) 子树内的所有点不一定能到达 \(u\)

于是考虑递归处理,若 \(v\)\(u\) 不连通就把这个子树作为 scc 之后删掉,然后回溯的时候就只剩能够到达节点 \(u\) 的子树了。

考虑用栈来维护,因为一个子树的 dfs 序是连续的,每次遇到这种情况就弹出一个子树即可。

\(\operatorname{dfn}_u\)\(u\) 的 dfs 序,\(\operatorname{low}_u\)\(u\) 能够到达的最小的 dfs 序。

无向图的 dfs 序没有横叉边,所以任意边 \((u,v)\) 中 dfs 序小的都是 dfs 序大的祖先。

所以我们判断是否 \(\operatorname{low}_u<\operatorname{dfn}_u\) 就可以知道 \(u\) 是否能到达其祖先了。

然后因为 \(\operatorname{low}_u\le \operatorname{dfn}_u\),所以我们通常判断这两个东西若相等则将这个子树删掉。

但是有向图的 dfs 序有横叉边,所以需要在接下来的处理中避免这个情况。

考虑遍历 \(u\) 的边 \((u,v)\)

  • \(v\) 没有被遍历过,即 dfs 序上的祖先。

递归处理 \(v\),然后 \(v\) 能够到达的点 \(u\) 肯定都能到达,所以有 \(\operatorname{low}_u\gets \operatorname{low}_v\)

  • \(v\) 被遍历过,要么是之前被遍历过的一个子树上的点(横叉边),要么是 \(u\) 祖先。

我们需要避免横叉边带来的影响。

即若这条边是横叉边,我们只在 \(v\) 能够到达 \(u\) 祖先的时候继承其贡献,否则不能继承。

直接考虑判断 \(v\) 是否有被删掉(即在不在栈中),如果这个点没有被删掉则 \(v\) 肯定能够到达 \(u\) 的祖先。

考虑这个条件是不是充要的,即一个点如果在栈中是否一定就是横叉边。

显然不一定,即 \(v\) 在栈中还可能是 \(u\) 的祖先。但是这种情况也可以转移,所以不影响答案。然后就没有其它情况了。

这个地方写 \(\operatorname{low}_u\gets \operatorname{low}_v\) 或者 \(\operatorname{low}_u\gets \operatorname{dfn}_v\) 感觉都可以,因为这两种情况下都有 \(\operatorname{dfn}_v<\operatorname{dfn}_u\)

B3609 [图论与代数结构 701] 强连通分量

#include<bits/stdc++.h>
#define sd std::
// #define int long long
#define F(i,a,b) for(int i=(a);i<=(b);i++)
#define ff(i,a,b) for(int i=(a);i>=(b);i--)
#define me(x,y) memset(x,y,sizeof x)
#define pii sd pair<int,int>
#define X first
#define Y second
#define dbg(x) sd cout<<#x<<":"<<x<<" "
#define dg(x) sd cout<<#x<<":"<<x<<"\n"
#define inf 1e10
int read(){int w=1,c=0;char ch=getchar();for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;for(;ch>='0'&&ch<='9';ch=getchar()) c=(c<<3)+(c<<1)+ch-48;return w*c;}
void printt(int x){if(x>9) printt(x/10);putchar(x%10+48);}
void print(int x){if(x<0) putchar('-'),printt(-x);else printt(x);}
void put(int x){print(x);putchar('\n');}
void printk(int x){print(x);putchar(' ');}
const int N=5e5+10,P=1e9+7;
int n,m,c;
int dfn[N],num,low[N],pos[N];
sd vector<int> scc[N],g[N];
sd stack<int> st;
int vis[N];
void dfs(int u)
{
	dfn[u]=low[u]=++num;
	vis[u]=1;
	st.push(u);
	for(auto v:g[u])
	{
		if(!dfn[v]) dfs(v),low[u]=sd min(low[u],low[v]);
		else if(vis[v]) low[u]=sd min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u])
	{
		++c;
		while(!st.empty())
		{
			int x=st.top();
			vis[x]=0;
			pos[x]=c;
			scc[c].emplace_back(x);
			if(x==u)
			{
				st.pop();
				break;
			}
			st.pop();
		}
	}
}
int flag[N];
void solve()
{
	n=read(),m=read();
	F(i,1,m)
	{
		int x=read(),y=read();
		if(x!=y) g[x].emplace_back(y);
	}
	F(i,1,n) if(!dfn[i]) dfs(i);
	put(c);
	F(i,1,n) if(!flag[i])
	{
		int p=pos[i];
		sd sort(scc[p].begin(),scc[p].end());
		for(auto u:scc[p]) printk(u),flag[u]=1;
		puts("");
	}
	
}
int main()
{
// 	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=1;
	// T=read();
	while(T--) solve();
    return 0;
}
posted @ 2025-09-24 11:53  _E_M_T  阅读(16)  评论(0)    收藏  举报