今天发的第三篇有关tarjan算法的文章了。
tarjan这个算法也是我刚学的,昨天做了APIO抢掠计划。一道tarjan+最长路。
硬是懵了一个下午,所以决定学tarjan。(有兴趣的同学可以去打一下)
好了,言归正传,这次的题目要靠tarjan缩点了(缩点是tarjan一个非常重要的用途)
题目描述
每头奶牛都梦想成为牛棚里的明星。被所有奶牛喜欢的奶牛就是一头明星奶牛。所有奶
牛都是自恋狂,每头奶牛总是喜欢自己的。奶牛之间的“喜欢”是可以传递的——如果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
我们可以先得出一个推论:在DAG(有向无环图)中,如果有且仅有一个点,它的出度为0,那么所有点都可以到这个点。
如图,图中有且仅有粉红色的点出度为0,而所有点都经过这个点。
而这张图中,黄色和粉红色都出度为0,所以此图没有任何一个点被所有点经过。
通过这个推论,我们可以将一张有向有环图用tarjan缩点的办法缩成一张有向无环图,
这样只要求出每个点的出度即可。
我们可以看此题样例:
粉色的点与红色的点构成一个环(强连通分量),所以可以用tarjan算法
将其缩成蓝色的点
此题的大致思路就是这样,代码细节较多,在程序里解释。
#include<bits/stdc++.h>
#include<vector>
using namespace std;
int i,j,k,n,m,tot,ans,top,co_num,ta_num,ansrecord,totrecord;
vector<int> f[10005];
int dfn[10005],low[10005],vis[10005],sta[10005],re[10005];
struct node{
int num;
vector<int> go,team;
}a[10005];
int read(){
char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
void tarjan(int x){
ta_num++;
dfn[x]=low[x]=ta_num;
vis[x]=1;sta[++top]=x;
for(int i=0;i<f[x].size();i++){
int temp=f[x][i];
if(!dfn[temp]){
tarjan(temp);low[x]=min(low[x],low[temp]);
}
else if(vis[temp]) low[x]=min(low[x],dfn[temp]);
}
if(dfn[x]==low[x]){
int now=0;
tot++;
while(now!=x){
now=sta[top];
top--;
a[tot].num++;
a[tot].team.push_back(now); //缩点,将一个强连通分量里的信息缩到a[tot]
vis[now]=0;
}
memset(re,0,sizeof(re));
for(int i=0;i<a[tot].team.size();i++)
re[a[tot].team[i]]=1;
for(int i=0;i<a[tot].team.size();i++){
int next=a[tot].team[i];
for(int j=0;j<f[next].size();j++)
if(!re[f[next][j]]) a[tot].go.push_back(f[next][j]);
} //这部将强连通分量所有点所走出的边缩到一起,注意将连接同一强连通分量的边去除
}
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;i++){
int x=read(),y=read();
f[x].push_back(y);
}
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
for(int i=1;i<=tot;i++)
if(a[i].go.empty()){ //找出度为0的点
totrecord++;
ansrecord=a[i].num;
}
if(totrecord==1) printf("%d",ansrecord);//判断是否只有一个点出度为0
else printf("0");
return 0;
}