首先对于一个强联通分量内的所有牛来说,他们彼此都认为对方受欢迎,且对于这个强联通分量内的牛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; }