EH's Blog

题目情境#

题目链接:https://www.luogu.com.cn/problem/P2863 ↗

题目描述

有一个 nn 个点,mm 条边的有向图,请求出这个图点数大于 11 的强连通分量个数。

输入格式

第一行为两个整数 nn 和 mm。

第二行至 m+1m+1 行,每一行有两个整数 aa 和 bb,表示有一条从 aa 到 bb 的有向边。

输出格式

仅一行,表示点数大于 11 的强连通分量个数。

输入 #1

5 4
2 4
3 5
1 2
4 1
plaintext

输出 #1

1
plaintext

对于全部的测试点,保证 2≤n≤1042\le n \le 10^4,2≤m≤5×1042\le m\le 5\times 10^4,1≤a,b≤n1 \leq a, b \leq n。

算法解析:#

image-20250817142047464

Tarjan 强连通分量:DFS 时给每个点记录 dfndfn(访问序)和 lowlow(子树内能回溯到的最早 dfndfn),用栈维护当前 SCC 候选。当 low[u]==dfn[u]low[u]==dfn[u] 时,uu 是 SCC 的根,把栈顶到 uu 的节点全部弹出作为一个 SCC。复杂度 O(n+m)O(n+m)。整个过程其实很纯粹,你品,你细品。

py代码实现#

略,涉及到dfs的都不适合写python。

C++代码实现#

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

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

void tarjan(int x){
  dfn[x]=low[x]=++tim; //时间戳 追溯值
  stk[++top]=x,ins[x]=1;
  for(int y:e[x]){
    if(!dfn[y]){ //若y尚未访问
      tarjan(y);
      low[x]=min(low[x],low[y]); //因y是儿子
    }
    else if(ins[y]) //若y已访问且在栈中
      low[x]=min(low[x],dfn[y]); //因y是祖先或左子树点
  }

  if(dfn[x]==low[x]){ //若x是SCC的根
    ++cnt;
    while(1){
      int y=stk[top--]; ins[y]=0;
      scc[y]=cnt; //SCC的编号
      ++siz[cnt]; //SCC的大小
      if(y==x) break;
    }
  }
}
int main(){
  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  cin>>n>>m;
  while(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;
}
c
D11 强连通分量Tarjan算法
https://ehnotgod.site/blog/d11-%E5%BC%BA%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8Ftarjan%E7%AE%97%E6%B3%95
Author EH
Published at August 8, 2026