复习心得 - 强连通分量
强连通分量 \tt SCC
图变为无向图可以互相到达的点集是弱连通分量。
任意两点可以互相可达的一个点集是强联通分量。
Tarjan 算法
边 dfs 边能形成
具体的:
- 要是没有访问过,
dfn_v=0 ,我们继续搜下去,我们要更新一个追溯值low_v\to low_p ,最早可以追溯到哪里。 - 否则要是这个点放入了当前的
\tt SCC ,那么dfn_v\to low_p ,找到当前\tt SCC 的最早追溯值。
然后我们发现可能这个点终结了这个
那么,我们从当前栈中找点,一直找到当前点
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,dfn[N],did,top,st[N],low[N],in[N],scid;
vector<int>e[N];
vector<vector<int>>scc;
void dfs(int p){
st[++top]=p;
++did;
low[p]=did;
dfn[p]=did;
in[p]=1;
for(auto v:e[p]){
if(!dfn[v]){
dfs(v);
low[p]=min(low[p],low[v]);
}else if(in[v])
low[p]=min(low[p],dfn[v]);
}
if(low[p]==dfn[p]){
int now;
vector<int>ns;
do{
now=st[top];
in[now]=0;
ns.push_back(now);
--top;
}while(now!=p);
sort(ns.begin(),ns.end());
scc.push_back(ns);
}
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
e[u].push_back(v);
}
for(int i=1;i<=n;i++)if(!dfn[i])
dfs(i);
sort(scc.begin(),scc.end());
for(auto sc:scc){
for(auto v:sc)cout<<v<<" ";
cout<<"\n";
}
return 0;
}
Kosaraju 算法
这个不太多人知道的算法是这样的:
- 首先正着 dfs 一次,搞出出栈序列。
- 然后按照出栈序列从后往前搜,每一次要是没被搜过说明是汇点,从这个点开始搜,最后求出强联通分量。
为什么是汇点呢?我们发现最后出栈那么说明所属强连通分量已经出栈了,肯定可以作为汇点去搜出原来的强连通分量。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,vis[N],st[N],top;
vector<int>e[N],g[N],c;
vector<vector<int>>scc;
void dfs(int p){
vis[p]=1;
for(auto v:e[p])if(!vis[v])
dfs(v);
st[++top]=p;
}
void dfs2(int p){
vis[p]=1;
for(auto v:g[p])if(!vis[v])
dfs2(v);
c.push_back(p);
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
e[u].push_back(v);
g[v].push_back(u);
}
for(int i=1;i<=n;i++)if(!vis[i])
dfs(i);
for(int i=1;i<=n;i++)
vis[i]=0;
for(int i=n;i>=1;i--)if(!vis[st[i]]){
c.clear();
dfs2(st[i]);
sort(c.begin(),c.end());
scc.push_back(c);
}
sort(scc.begin(),scc.end());
for(auto sc:scc){
for(auto v:sc)cout<<v<<" ";
cout<<"\n";
}
return 0;
}