问题描述
思路
tarjan算法 + 缩点
先把一个图的各个强连通分量求出(使用tarjan算法),接着用缩点的形式构建一个有向无环图(把各个强连通分量看成一个点,利用点与点之间的关系,构建一个强连通分量之间的有向图,而且这个图一定是无环的图)
然后分析这个有向无环图的出入度情况,出度为0,入度不为0,那这个强连通分量里的点是可能被所有的点到达的
ac代码
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int MAX_N=10001;
const int MAX_M=50001;
struct edge{
int u,v,next;
edge(){}
edge(int _u,int _v,int _n):u(_u),v(_v),next(_n){}
}e[MAX_M];
int p[MAX_N];
int belong[MAX_N],scc=0;//统计各个点属于那个连通分量,连通分量数
int idx=0;//时间戳
int dfn[MAX_N],low[MAX_N];//dfn[i]表示点i被tarjan访问的时间,low[i]表示i及i的子树在栈中出现的最早的时间。
int s[MAX_N],top=0;//模拟栈
bool in_stack[MAX_N];//标记是否在栈中
int in[MAX_N],out[MAX_N];//统计缩点之后的有向无环图图的出入度情况
void tarjan(int u){
dfn[u]=low[u]=++idx;//先记录改点的时间戳
s[top++]=u;//放入栈中
in_stack[u]=true;//标记已放入栈中
for(int i=p[u];i+1;i=e[i].next){//遍历u指向的邻边
int v=e[i].v;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);//回溯之后更新low[u]为点u及u点子树的在栈中出现最早的时间
}
else if(in_stack[v]){
low[u]=min(low[u],dfn[v]);//成环,将low[u]更新成最早出现的时间
}
}
if(dfn[u]==low[u]){//是根
scc++;
do{
belong[s[--top]]=scc;//标记为第scc号连通分量的点
in_stack[s[top]]=false;//出栈标记
}while(s[top]!=u);
}
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
memset(p,-1,sizeof(p));
for(int i=0;i<m;++i){
int u,v;
scanf("%d%d",&u,&v);
e[i]=edge(u,v,p[u]);
p[u]=i;
}
memset(dfn,0,sizeof(dfn));
memset(in_stack,false,sizeof(in_stack));
for(int i=1;i<=n;++i){
if(!dfn[i]){
tarjan(i);
}
}
if(scc==1){printf("%d\n",n);return 0;}//就一个强连通分量,那任意两个点都是可以互相到达的
memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
for(int i=0;i<m;++i){//缩点
int u=e[i].u;
int v=e[i].v;
if(belong[u]!=belong[v]){//统计个点的出入度情况
out[belong[u]]++;
in[belong[v]]++;
}
}
int std_scc=0;
for(int i=1;i<=scc;++i){
if(in[i]>0&&out[i]==0){//存在两个 “不能到达其他地方的点,只能由其它点到达的点” ,那任何点都不可能被其他所有点到达
if(std_scc){
printf("0\n");
return 0;
}
std_scc=i;
}
}
int sum=0;
for(int j=1;j<=n;++j){
if(belong[j]==std_scc)
sum++;
}
printf("%d\n",sum);
return 0;//give me five
}