题目背景
本题测试数据已修复。
题目描述
每头奶牛都梦想成为牛棚里的明星。被所有奶牛喜欢的奶牛就是一头明星奶牛。所有奶
牛都是自恋狂,每头奶牛总是喜欢自己的。奶牛之间的“喜欢”是可以传递的——如果A喜
欢B,B喜欢C,那么A也喜欢C。牛栏里共有N 头奶牛,给定一些奶牛之间的爱慕关系,请你
算出有多少头奶牛可以当明星。
输入输出格式
输入格式:
第一行:两个用空格分开的整数:N和M
第二行到第M + 1行:每行两个用空格分开的整数:A和B,表示A喜欢B
输出格式:
第一行:单独一个整数,表示明星奶牛的数量
输入输出样例
输入样例#1:
3 3
1 2
2 1
2 3
输出样例#1:
1
说明
只有 3 号奶牛可以做明星
【数据范围】
10%的数据N<=20, M<=50
30%的数据N<=1000,M<=20000
70%的数据N<=5000,M<=50000
100%的数据N<=10000,M<=50000
题解
对于本题各位大佬的愿望肯定是AC,所以我们直接讲满分算法
1.题目分析
本题的意思大概就是我们选出的明星牛每头牛都喜欢,将牛看做一个点,牛与牛之间的喜欢看做一条路
,那么我们是不是就相当于在一个有向图中去找强连通子图了对吧
2.主要算法分析
(1)因为同一头牛被所有牛喜欢,那么它不能喜欢任意除了自己这个联通子图的牛的其他牛,所以在每个联通子图下统计是否出度为0,就好了。同样,如果存在两个或两个以上的出度为0的点(已经缩点之后),那么直接输出0就好了。
(2)那么既然要找强连通子图,就必须要用到Tarjan算法
我摘抄一段Tarjan算法的讲解(懒。。)
Tarjan算法介绍:
Tarjan算法是图论中的一种算法,用作于图的联通性
它可以做什么?
根据 Robert Tarjan 的名字命名的算法Tarjan算法可以在线性时间内求出无向图的割点与桥,再进一步的求出双联通分量,也在数据结构上做出了贡献。
Tarjan算法的用途
1.求桥和割点
2.求点和边的双连通分量
3.求强连通*
做法基础
Tarjan算法基于图的深度优先遍历上(没错!就是与深度优先搜索(DFS)一样的东西)
(如果没学过这样东西的人可以先收藏一下,等学过了在看)
Targan算法的流程
利用dfs来遍历图来构建一种数型的结构
Tarjan算法的两个核心数组
dfn:我们用dfn数组记录
low:我们用low[i]表示一个节点的子树中可以到达最小的dfn
(显然对于一个刚刚遍历到的点我们给他赋上一个新的dfn,low)
摘自:huangdanning-HDN
3.tarjan算法完美结束之后
我们需要进行判断该点及其子孙是否能够够成一个强连通子图,这里只需要判断一下
dfn[u]==low[u]就好了
4.对于2 ,3给出片段代码
代码注释很详细
void tarjan(int u)
{
num++;
dfn[u]=num;//时间戳
low[u]=num;//初始化这个low
st[++top]=u;//入栈
for(int i=first[u];i;i=next[i])//邻接表遍历
{
int v=to[i];//存储到达的点
if(!dfn[v])//如果该点还没有被扫过,时间戳为0
{
tarjan(v);//那就向下扫方便找low
low[u]=min(low[u],low[v]);//low取最小值,看看能不能成环
}
else if(!co[v])//判断是否在栈中
{
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u])//如果相等就记录,出栈
{
co[u]=++col;//co存强连通分量元素 ,其值代表在第几个强连通子图中
++si[col];//代表每个强联通子图中对应的元素个数
while(st[top]!=u)
{
++si[col];
co[st[top]]=col;
--top;
}
--top;//将st【top】(==u)出栈
}
}
5.因为题目中给出的数据极大如果用邻接矩阵势必要超出空间,所以我们需要使用
邻接表
来存图,如下,有兴趣的可以试一试前向星
大致内容就是开三个数组一个存点,另外两个存边,代码中有解释
void getsin(int a,int b)
{
total++;
to[total]=b;//to来存储点
next[total]=first[a];//next表示接下来的边
first[a]=total;//first表示该点连的第一条边
}
cin>>n>>m;
for(int i=1,x,y;i<=m;i++)
{
scanf("%d %d",&x,&y);
getsin(y,x);//将出度巧妙转化为入度
}
附上AC代码
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#define maxn 100005
using namespace std;
int n,m;
int to[maxn];
int num;
int st[maxn];
int stack[maxn];
int top;
int col,dfn[maxn],low[maxn],de[maxn],si[maxn];
int co[maxn];
int next[maxn],first[maxn];
int total;
void getsin(int a,int b)
{
total++;
to[total]=b;
next[total]=first[a];
first[a]=total;
}
void tarjan(int u)
{
num++;
dfn[u]=num;
low[u]=num;
st[++top]=u;
for(int i=first[u];i;i=next[i])
{
int v=to[i];
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(!co[v])
{
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u])
{
co[u]=++col;
++si[col];
while(st[top]!=u)
{
++si[col];
co[st[top]]=col;
--top;
}
--top;
}
}
int main()
{
cin>>n>>m;
for(int i=1,x,y;i<=m;i++)
{
scanf("%d %d",&x,&y);
getsin(y,x);
}
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;j=next[j])
{
if(co[i]!=co[to[j]])//在不同的强连通图中
de[co[to[j]]]++;//求入度
}
}
int ans=0;
int u=0;
for(int i=1;i<=col;i++)
{
if(!de[i])//如果入度为零说明为明星牛
{
ans=si[i];//si【i】代表的图中一共有多少头牛
u++;
}
}
if(u==1)
cout<<ans;
else
cout<<"0";//如果超过2个或等于0都是没有明星牛的
while(1)
cout<<"Sabrina"<<endl//防抄
return 0;
}