D14【模板】强连通分量 Tarjan 算法

D14【模板】强连通分量 Tarjan 算法_哔哩哔哩_bilibili

 

强连通:若一张有向图的节点两两互相可达,则称这张图是强连通的

强连通分量 (Strongly Connected Components,SCC):极大的强连通子图

图中的 SCC 有 3 个: {1,2,3,4,9}, {5,6,8}, {7}

对图深搜时,每一个节点只访问一次,被访问过的节点与边构成一颗 搜索树

有向边按访问情况分 4 类:

  1. 树边 (tree edge):访问节点走过的边。图中的黑色边
  2. 返祖边 (back edge):指向祖先节点的边。图中的红色边
  3. 横叉边 (cross edge):右子树指向左子树的边。图中的绿色边
  4. 前向边 (forward edge):指向子树中节点的边。图中的蓝色边

返祖边与树边必构成环,横叉边可能与树边构成环, 前向边无用

如果节点 x 是某个强连通分量在搜索树中遇到的第一个节点,那么这个强连通分量的其余节点肯定是在搜索树中以 x 为根的子树中

节点 x 被称为这个强连通分量的根

枚举 x 的邻点 y,分三种情况:

  1. 若 y 尚未访问:对 y 深搜。回 x时,用 low[y] 更新 low[x]。因为 x 是 y 的父节点,y 能访问到的点,x 一定也能访问到
  2. 若 y 已访问且在栈中:说明 y 是祖先节点或左子树节点,用 dfn[y] 更新 low[x]
  3. 若 y 已访问且不在栈中:说明 y 已搜索完毕,其所在连通分量已被处理,所以不用对其做操作

注意:有向有环图缩点后变成 DAG,缩点的编号是拓扑序的逆序,起点的编号最大,终点的编号最小

 

P2863 [USACO06JAN] The Cow Prom S - 洛谷

// Tarjan算法 O(n+m)
#include<bits/stdc++.h>
using namespace std;

const int N=10010;
int n,m,ans;
vector<int> e[N]; 
int dfn[N],low[N],tim,stk[N],top,scc[N],siz[N],cnt;

void tarjan(int u){
  dfn[u]=low[u]=++tim; stk[++top]=u;
  for(int v:e[u]){
    if(!dfn[v]){ //若v尚未访问
      tarjan(v);
      low[u]=min(low[u],low[v]);
    }
    else if(!scc[v]) //若v已访问且未构成SCC
      low[u]=min(low[u],dfn[v]);
  }

  if(low[u]==dfn[u]){ //若u不是SCC的根,则low<dfn
    ++cnt;
    for(int v=-1;v!=u;){
      v=stk[top--];
      scc[v]=cnt; //SCC的编号
      ++siz[cnt]; //SCC的大小
    }
  }
}
int main(){
  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  cin>>n>>m;
  for(int a,b;m--;) cin>>a>>b,e[a].push_back(b);
  for(int i=1;i<=n;i++)if(!dfn[i]) tarjan(i);
  for(int i=1;i<=cnt;i++)if(siz[i]>1) ans++;
  cout<<ans;
}

 

B3609 [图论与代数结构 701] 强连通分量 - 洛谷

 

posted @ 2022-05-28 13:28  董晓  阅读(3093)  评论(1)    收藏  举报