P2863 [USACO06JAN]The Cow Prom S (tarjan 求SCC)
[USACO06JAN]The Cow Prom S
题目描述
有一个 \(n\) 个点,\(m\) 条边的有向图,请求出这个图点数大于 \(1\) 的强联通分量个数。
输入格式
第一行为两个整数 \(n\) 和 \(m\)。
第二行至 \(m+1\) 行,每一行有两个整数 \(a\) 和 \(b\),表示有一条从 \(a\) 到 \(b\) 的有向边。
输出格式
仅一行,表示点数大于 \(1\) 的强联通分量个数。
样例 #1
样例输入 #1
5 4
2 4
3 5
1 2
4 1
样例输出 #1
1
提示
数据规模与约定
对于全部的测试点,保证 \(2\le n \le 10^4\),\(2\le m\le 5\times 10^4\),\(1 \leq a, b \leq n\)。
代码
// Problem: P2863 [USACO06JAN]The Cow Prom S
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2863
// Memory Limit: 128 MB
// Time Limit: 1000 ms
// Created Time: 2022-07-15 09:29:12
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<bits/stdc++.h>
#define zp ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=1e4+10,M=5e4+5;
int h[N],e[M],ne[M],idx;
int n,m,dfn[N],low[N],times;
int stk[N],ttop;
bool st[N];
int id[N],scc_cnt,siz[N];
void add(int a,int b)
{
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
void tarjan(int u)
{
dfn[u]=low[u]=++times;//初始化时间戳和能访问到的最小时间戳
//不能写成times++,因为是以dfn==0为条件判断是否被计算过的
stk[++ttop]=u,st[u]=true;//把当前点加入联通分量的栈
//写++ttop是为了后面取出栈顶元素是对应,这里也可以写ttop++,同时将后面改成t=stk[--ttop]
for(int i=h[u];~i;i=ne[i])//遍历当前点能访问到的所有邻点
{
int j=e[i];
if(!dfn[j])//如果没有计算过
{
tarjan(j);//递归计算
low[u]=min(low[u],low[j]);//用邻点能访问到的最小时间戳更新当前点
}
else if (st[j])low[u]=min(low[u],dfn[j]);//如果已经在栈中就用时间戳更新
}
if(low[u]==dfn[u])//如果这个点是联通分量中的最高点
{
++scc_cnt;//计数
int y;
//记录该联通分量中所有点的信息
do{
y=stk[ttop--];
st[y]=false;
id[y]=scc_cnt;
siz[scc_cnt]++;
}while(y!=u);
}
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
//前向星建图
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b);
}
//tarjan求SCC
for(int i=1;i<=n;i++)
if(!dfn[i])//如果未被计算过就计算
tarjan(i);
int ans=0;
for(int i=1;i<=scc_cnt;i++)
{
if(siz[i]>1)ans++;//统计点数大于1的SCC
}
cout<<ans<<endl;
return 0;
}
一个菜鸡