每一头牛的愿望就是变成一头最受欢迎的牛。
现在有 N 头牛,编号从 1 到 N,给你 M 对整数 (A,B),表示牛 A 认为牛 B 受欢迎。
这种关系是具有传递性的,如果 A 认为 B 受欢迎,B 认为 C 受欢迎,那么牛 A 也认为牛 C 受欢迎。
你的任务是求出有多少头牛被除自己之外的所有牛认为是受欢迎的。
输入格式
第一行两个数 N,M;
接下来 M 行,每行两个数 A,B,意思是 A 认为 B 是受欢迎的(给出的信息有可能重复,即有可能出现多个 A,B)。
输出格式
输出被除自己之外的所有牛认为是受欢迎的牛的数量。
数据范围
1≤N≤10^4,
1≤M≤5×10^4
输入样例:
3 3
1 2
2 1
2 3
输出样例:
1
样例解释
只有第三头牛被除自己之外的所有牛认为是受欢迎的。
分析题意后我们发现这道题的描述的符合题意的点和拓扑图中的出度为0的点极为相似
而且我们知道一个连通分量中的点都是可以互相到达的,也就是说如果可以找到唯一一个出度为0的连通分量,那么这个连通分量中的所有点都是满足题意的
这和tarjan算法的思路不谋而合,做完一遍tarjan之后我们就可以把互相可以到达的点都放到同一个连通分量之中
而且我们在做完一遍tarjan之后所有连通分量之间的关系是逆拓扑序,那么意味着我们在做完tarjan之后就不需要拓扑排序了,只需要把入度为0的连通分量统计一下就可
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
const int N = 100010;
using namespace std;
int n,m;
int h[N],ne[N],e[N],idx;
int dfn[N],low[N],timestamp; //两个时间戳数组还有时间戳编号
int stk[N],top; //栈数组,栈元素编号
bool in_stk[N]; //判断是否在栈中
int id[N],scc_cnt,s[N]; //连通分量编号,每个点属于哪个连通分量以及没每个连通分量有哪些点
int dout[N]; //每个点的出度,用来判断出度为0的点的个数
void add(int a,int b)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++timestamp;
stk[++top] = u,in_stk[u] = 1;
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(in_stk[j]) low[u] = min(low[u],dfn[j]);
}
if(dfn[u]==low[u]) //往栈里存元素+出栈
{
++scc_cnt;
int y;
do
{
y = stk[top--]; //出栈
in_stk[y] = 0; //标记点
id[y] = scc_cnt;
s[scc_cnt]++; //记录栈中元素个数
}while(y!=u);
}
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b);
}
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++)
for(int j=h[i];~j;j=ne[j])
{
int k = e[j];
int a = id[i],b = id[k];
if(a!=b) dout[a]++;
}
int zcnt = 0,sum = 0;
for(int i=1;i<=scc_cnt;i++)
{
if(!dout[i])
{
zcnt++;
sum += s[i];
}
if(zcnt>1) sum = 0;
}
cout<<sum;
return 0;
}
要加油!努力!