点-双连通分量:任意两点之间至少存在两条“点不重复”的路径。等价于内部无割点
边-双连通分量:任意两点之间至少存在两条“边不重复”的路径。等价于内部无桥
Tarjan
void dfs(int u){
pre[u] = lowlink[u] = ++cnt;
st.push(u);
for(int i=0;i<G[u].size();i++){
int v = G[u][i];
if(!pre[v]){ //之前没有访问过
dfs(v);
lowlink[u] = min(lowlink[u],lowlink[v]);
}else if(!sccno[v]){ //遇到之前访问过的了
lowlink[u] = min(pre[v],lowlink[u]);
}
}
if(pre[u] == lowlink[u]){
scc++;
while(1){
int x = st.top(); st.pop();
sccno[x] = scc;
if(x == u) break;
}
}
}
void Tarjan(){
cnt = scc = 0;
memset(pre,0,sizeof(pre));
memset(sccno,0,sizeof(sccno));
for(int i=1;i<=n;i++)
if(!pre[i]) dfs(i);
}
Tarjan缩点
for(int i=1;i<=n;i++){
for(int j=first[i];j;j=ne[j]){
int v = to[j];
if(sccno[i] != sccno[v]){
G[sccno[i]].push_back(sccno[v]);
G[sccno[v]].push_back(sccno[i]);
}
}
}
Tarjan求桥
void dfs(int u,int fa){
dfn[u] = lowlink[u] = ++cnt;
st.push(u);
int num = 0;
for(int i=first[u];i;i=ne[i]){
int v = to[i];
if(v == fa){
num++;
if(num == 1) continue; //因为无向图有反向边,所以第一次搜到的不是重边。
}
if(!dfn[v]){
dfs(v,u);
lowlink[u] = min(lowlink[v],lowlink[u]);
if(lowlink[v] > dfn[u]) bridge++;
}else if(!sccno[v]){
lowlink[u] = min(lowlink[u],dfn[v]);
}
}
if(dfn[u] == lowlink[u]){
scc++;
while(1){
int x = st.top(); st.pop();
sccno[x]= scc;
if(x == u) break;
}
}
}
void Tarjan(){
cnt = scc = 0;
memset(pre,0,sizeof(pre));
memset(sccno,0,sizeof(sccno));
dfs(1); //桥的前提是无向连通图,所以dfs一次就遍历所有的点了。
}
Tarjan求割点
void dfs(int u){
pre[u] = lowlink[u] = ++cnt;
st.push(u);
for(int i=0;i<G[u].size();i++){
int v = G[u][i];
if(!pre[v]){
dfs(v);
lowlink[u] = min(lowlink[u],lowlink[v]);
if(lowlink[v] >= pre[u] && u != root) ans[u] = true;
else if(u == root) son++; //只有son > 1是根节点内才是割点
}else if(!sccno[v]){
lowlink[u] = min(lowlink[u],pre[v]);
}
}
if(pre[u] == lowlink[u]){
scc++;
while(1){
int x = st.top(); st.pop();
sccno[x] = scc;
if(x == u) break;
}
}
}
void Tarjan(){
cnt = scc = 0;
memset(pre,0,sizeof(pre));
memset(sccno,0,sizeof(sccno));
dfs(1); //割点遍历一次
}
Kosaraju
void dfs(int u){
vis[u] = true;
for(int i=0;i<G1[u].size();i++){
int v = G1[u][i];
if(vis[v]) continue;
vis[v] = true;
dfs(v);
}
s.push_back(u); //反向记录拓扑序列
}
void dfs2(int u){
vis[u] = true;
sccno[u] = scc; //连通分块的编号
for(int i=0;i<G2[u].size();i++){
int v = G2[u][i];
if(vis[v]) continue;
dfs2(v);
}
}
int main(){
while(~scanf("%d%d",&n,&m)){
s.clear();
for(int i=0;i<=n;i++) G1[i].clear(), G2[i].clear();
for(int i=0;i<m;i++){
int from,to;
scanf("%d%d",&from,&to);
G1[from].push_back(to);
G2[to].push_back(from);
}
memset(vis,false,sizeof(vis));
for(int i=1;i<=n;i++)
if(!vis[i]) dfs(i);
memset(vis,false,sizeof(vis));
for(int i=n-1;i>=0;i--){
if(vis[s[i]]) continue;
scc++; dfs2(s[i]);
}
}