tarjan 求强连通分量学习笔记

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

定义

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

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

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

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

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

tarjan

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

我们考虑在遇到每个强连通分量中在 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  阅读(4)  评论(0)    收藏  举报