前言:这个taryan算法其实本质上就是一个dfs,我们会先处理晚访问的节点(这个不去模拟一遍是很难理解的)
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N = (int)5e4+5;
int e[N],ne[N],h[N],idx = 0;
int dfn[N],low[N],tot = 0; // 一个是时间戳一个是追溯值
int stk[N],instk[N],top = 0; // 模拟栈
int scc[N],siz[N],cnt = 0; // 记录答案
void add(int a,int b){
e[++idx] = b , ne[idx] = h[a] , h[a] = idx;
}
void taryan(int x){
// 先入栈以及编号
dfn[x] = ++tot, low[x] = tot;
stk[++top] = x, instk[x] = 1;
for(int i=h[x];i;i=ne[i]){
int to = e[i];
if(!dfn[to]){
// 没被访问
taryan(to);
low[x] = min(low[x],low[to]); // 返回的时候同时更新
}else if(instk[to]){
low[x] = min(low[x],dfn[to]);
// 这一点值得注意,一定要是在
// 栈中的
// 这个说明 y 是 x 的祖先节点或者左边节点
// 为什么这个是 dfn 而不是 low 其实这两个是等价的
}
}
// 离开x,记录以 x 为根的强联通分量
if(low[x]==dfn[x]){
int y;++cnt;
do{
y = stk[top--] , instk[y] = 0;
scc[y] = cnt, siz[cnt]++; // 更新强联通分量的编号以及大小
}while(x!=y);
}
}
int main(){
cin >> n >> m;
for(int i=1;i<=m;i++){
int u,v; cin >> u >> v;
add(u,v);
}
for(int i=1;i<=n;i++){
if(scc[i]==0){
taryan(i);
}
}int ans = 0;
for(int i=1;i<=cnt;i++){
if(siz[i]>1) ans++;
}
cout << ans;
return 0;
}