原题链接:https://www.acwing.com/problem/content/1176/
每一头牛的愿望就是变成一头最受欢迎的牛。
现在有 N 头牛,编号从 1 到 N,给你 MM 对整数 (A,B),表示牛 A 认为牛 B 受欢迎。
这种关系是具有传递性的,如果 A 认为 B 受欢迎,B 认为 C 受欢迎,那么牛 A 也认为牛 C 受欢迎。
你的任务是求出有多少头牛被除自己之外的所有牛认为是受欢迎的。
输入格式
第一行两个数 N,M;
接下来 M 行,每行两个数 A,B,意思是 A认为 B 是受欢迎的(给出的信息有可能重复,即有可能出现多个 A,B)。
输出格式
输出被除自己之外的所有牛认为是受欢迎的牛的数量。
数据范围
1≤N≤1e4,
1≤M≤5e4
输入样例:
3 3
1 2
2 1
2 3
输出样例:
1
样例解释
只有第三头牛被除自己之外的所有牛认为是受欢迎的。
思路:把牛分成一堆一堆互相欢迎的牛,那么只有被所有堆觉得欢迎的那堆牛才算被除自己之外的所有牛认为是受欢迎的。这时候,觉得别的堆受欢迎的那堆牛一定不是答案(不然两堆牛就可以合并了),所以没有认为别的堆受欢迎的那堆牛就是答案。如果不止一堆牛没有认为别的堆受欢迎,则答案为0.
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10,M=5e4+10;
int n,m;
int first[N],ne[M],v[M],num_edge;
//dfn用于记录进入时间戳,low用于记录强连通分量中的最小时间戳
//timestamp为时间戳
int dfn[N],low[N],timestamp=0;
//sta为模拟栈,is_sta为某个点是否在栈中的标记
int sta[N],top;
bool is_sta[N];
//id为某个数属于哪个强连通分量
//scc_size为某个强连通分量中的点的个数
//scc_cnt为强连通分量的个数
int id[N],scc_size[N],scc_cnt;
//out用于记录某个强连通分量是否有出边
bool out[N];
void add(int x,int y){
ne[++num_edge]=first[x];
v[num_edge]=y;
first[x]=num_edge;
}
void tarjan(int x){
//赋初值并入栈
dfn[x]=low[x]=++timestamp;
sta[++top]=x,is_sta[x]=true;
//遍历子树,并更新low[x]
int u;
for(int i = first[x]; i!=-1; i=ne[i]){
u=v[i];
if(!dfn[u]){//还没遍历过的点
tarjan(u);
low[x]=min(low[x],low[u]);
}
else if(is_sta[u])
low[x]=min(low[x],low[u]);
}
//从最高的点建一个强连通分量
if(dfn[x]==low[x]){
scc_cnt++;
do{
//出栈
u=sta[top--];
is_sta[u]=false;
//入连通分量
scc_size[scc_cnt]++;
id[u]=scc_cnt;
}while(u!=x);
}
}
int main(){
int x,y;
scanf("%d%d",&n,&m);
memset(first,-1,sizeof(first));
//建图
while(m--){
scanf("%d%d",&x,&y);
add(x,y);
}
//找强连通分量
for(int i = 1; i <= n; i++)
if(!dfn[i])
tarjan(i);
//看每个强连通分量是否有出边
for(int i = 1; i <= n; i++)
for(int j = first[i]; j!=-1; j=ne[j]){
x=id[i],y=id[v[j]];
if(x!=y)out[x]=true;
}
//找唯一没有出边的强连通分量
int sum=0;
for(int i = 1; i <= scc_cnt; i++){
if(!out[i]){
if(sum==0)sum=scc_size[i];
else{//不止一个强连通分量没有出边,即没有答案
sum=0;
break;
}
}
}
printf("%d\n",sum);
}