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;
}

浙公网安备 33010602011771号