强联通分量(SCC)
有向图中的一个极大子图,其中任意两个节点 u 和 v 都互相可达(即存在 u→v和 v→u的路径),则这个子图为一个强联通分量
Tarjan 算法基于深度优先搜索(DFS),利用 DFS 序 dfn 和 low 链 来判断一个节点是否是某个强连通分量的“根”(即最早被访问的节点)。
基本定义:
强连通分量(SCC):有向图中,任意两个节点 u, v 互相可达的最大子图。
dfn[u]:节点 u 被 DFS 第一次访问的时间戳(DFS 序)。
low[u]:从 u 出发,通过若干条边(可以走树边、后向边、横叉边),能到达的所有节点中 最小的 dfn 值。
换句话说:low[u] = min{ dfn[v] | v 从 u 出发可达 }
关键性质:
如果在 DFS 回溯时发现 low[u] == dfn[u],说明 u 是当前 SCC 的“根”。
因为从 u 出发无法回到比 u 更早访问的节点。
此时,从栈顶到 u 的所有节点构成一个 SCC。
栈的作用:
DFS 过程中,将访问的节点压入栈。 当找到一个 SCC 的根时,从栈中弹出直到 u,这些节点就属于同一个 SCC。
模板
说明: void Run(int _n,const vector<int>adj[])为传入点的数量,vector邻接表数组,运行求出所有强联通分量 。·vector<int> Get()为获取scc数组,即scc[i]为 i 点属于的强连通分量编号
template<int N> struct SCC{
vector<int> scc;
vector<int> stk;
int dfn[N],low[N];
const vector<int>* adj;
int n,clk,tot; //tot:scc数量
void dfs(int u){
++clk;
dfn[u]=low[u]=clk;
stk.push_back(u);
for(int to:adj[u]){
if(dfn[to]==-1){
dfs(to);
low[u]=min(low[u],low[to]);
}
else if(scc[to]==-1) low[u]=min(low[u],dfn[to]);
}
if(low[u]==dfn[u]){
int v=-1;
++tot;
do{
v=stk.back();
stk.pop_back();
scc[v]=tot;
}while(v!=u);
}
}
void Run(int _n,const vector<int>adj[]){
n=_n;
clk=tot=0;
stk.clear();
fill(low,low+3+n,-1);
fill(dfn,dfn+3+n,-1);
scc.assign(n+3,-1);
this->adj=adj;
for(int i=1;i<=n;++i) if(scc[i]==-1) dfs(i);
}
vector<int> Get(){
return scc;
}
};
使用示例
template<int N> struct SCC{
vector<int> scc;
vector<int> stk;
int dfn[N],low[N];
const vector<int>* adj;
int n,clk,tot; //tot:scc数量
void dfs(int u){
++clk;
dfn[u]=low[u]=clk;
stk.push_back(u);
for(int to:adj[u]){
if(dfn[to]==-1){
dfs(to);
low[u]=min(low[u],low[to]);
}
else if(scc[to]==-1) low[u]=min(low[u],dfn[to]);
}
if(low[u]==dfn[u]){
int v=-1;
++tot;
do{
v=stk.back();
stk.pop_back();
scc[v]=tot;
}while(v!=u);
}
}
void Run(int _n,const vector<int>adj[]){
n=_n;
clk=tot=0;
stk.clear();
fill(low,low+1+n,-1);
fill(dfn,dfn+1+n,-1);
scc.assign(n+3,-1);
this->adj=adj;
for(int i=1;i<=n;++i) if(scc[i]==-1) dfs(i);
}
vector<int> Get(){
return scc;
}
};
const int maxn=2*1e5+20;
SCC<maxn> T;
vector<int> e[maxn];
int main(){
int n,m; //n个点,m条边
for(int i=1;i<=m;++i){ //输入m条边
int u,v;
cin >> u >> v;
e[u].push_back(v);
}
T.Run(n,e); //跑tarjan
vector<int> scc=T.Get(); //获取scc数组,scc[i]=i点属于的强连通分量编号