首先对于一个强联通分量内的所有牛来说,他们彼此都认为对方受欢迎,且对于这个强联通分量内的牛A来说,假设它认为不在这个强连通分量内的一头牛B是受欢迎的,那么这个强联通分量内的所有牛都认为牛B受欢迎。
我们用Tarjan算法求一遍SCC,把一个SCC缩成一个点,并添加连接不同SCC的边,注意这条边是一条反向边,本来的边由a->b,我们要添加的这条边由scc[b]->scc[a],这样做是为了方便之后的DFS,最后得到一个DAG。
接下来我们在这个DAG上从所有入度为0的scc开始DFS,并记录DFS过程中访问到的SCC个数记为cnt,如果DFS结束后cnt==scc_cnt,代表所有SCC都认为这个SCC是受欢迎的,则所有牛都认为这个SCC内的牛是受欢迎的,累计牛的个数进答案即可。
我们只需要从入度为0的SCC开始DFS,是因为假设有一条边SCC_A->SCC_B,代表SCC_B认为SCC_A是受欢迎的,又因为图是DAG,所以SCC_A一定不认为SCC_B是受欢迎的,那么SCC_B一定无法达到被所有牛认为是受欢迎的条件,所以我们证明了入度不为0的SCC一定不会是解。
// q.c
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<stack>
using namespace std;
const int M=10000+10;
int n,m,ans,cnt1,cnt2,dc,scc_cnt,scc[M],h1[M],h2[M],w[M],pre[M],low[M],in[M],out[M];
bool vis[M];
stack<int> s;
struct Edge {
int u,v,next;
Edge():u(0),v(0),next(-1) {}
}ed1[M*5],ed2[M*5];
void add_edge(int a,int b,bool flag) {
if(flag) ed1[++cnt1].u=a,ed1[cnt1].v=b,ed1[cnt1].next=h1[a],h1[a]=cnt1;
else in[b]++,out[a]++,ed2[++cnt2].u=a,ed2[cnt2].v=b,ed2[cnt2].next=h2[a],h2[a]=cnt2;
}
void dfs1(int u) {
pre[u]=low[u]=++dc;
s.push(u);
for(int i=h1[u];i!=-1;i=ed1[i].next) {
Edge p=ed1[i];
if(!pre[p.v]) {
dfs1(p.v);
low[u]=min(low[u],low[p.v]);
} else if(!scc[p.v])
low[u]=min(low[u],low[p.v]);
}
if(low[u]==pre[u]) {
++scc_cnt;
for(;;) {
int x=s.top(); s.pop();
scc[x]=scc_cnt;
if(x==u) break;
}
}
}
void dfs2(int u,int &d) {
vis[u]=true; ++d;
for(int i=h2[u];i!=-1;i=ed2[i].next)
if(!vis[ed2[i].v]) dfs2(ed2[i].v,d);
}
int main() {
freopen("cow.in","r",stdin);
freopen("cow.out","w",stdout);
memset(h1,-1,sizeof(h1));
memset(h2,-1,sizeof(h2));
scanf("%d%d",&n,&m);
int a,b,tot;
for(int i=1;i<=m;i++) {
scanf("%d%d",&a,&b);
add_edge(a,b,1);
}
for(int i=1;i<=n;i++)
if(!pre[i]) dfs1(i);
for(int i=1;i<=n;i++) w[scc[i]]++;
for(int i=1;i<=cnt1;i++) {
Edge p=ed1[i];
if(scc[p.u]!=scc[p.v]) add_edge(scc[p.v],scc[p.u],0);
}
for(int i=1;i<=scc_cnt;i++)
if(!in[i]) {
memset(vis,0,sizeof(vis));
dfs2(i,tot=0);
if(tot==scc_cnt) ans+=w[i];
}
printf("%d\n",ans);
return 0;
}