poj 2186 "Popular Cows"(强连通分量入门题)
参考资料:
[1]:挑战程序设计竞赛
题意:
每头牛都想成为牛群中的红人。
给定N头牛的牛群和M个有序对(A, B),(A, B)表示牛A认为牛B是红人;
该关系具有传递性,所以如果牛A认为牛B是红人,牛B认为牛C是红人,那么牛A也认为牛C是红人。
不过,给定的有序对中可能包含(A, B)和(B, C),但不包含(A, C)。
求被其他所有牛认为是红人的牛的总数。
分析(摘抄自挑战程序设计竞赛):
考虑以牛为顶点的有向图,对每个有序对(A, B)连一条从 A到B的有向边;
那么,被其他所有牛认为是红人的牛对应的顶点,也就是从其他所有顶点都可达的顶点。
虽然这可以通过从每个顶点出发搜索求得,但总的复杂度却是O(NM),是不可行的,必须要考虑更为高效的算法。
假设有两头牛A和B都被其他所有牛认为是红人,那么显然,A被B认为是红人,B也被A认为是红人;
即存在一个包含A、B两个顶点的圈,或者说,A、B同属于一个强连通分量。
反之,如果一头牛被其他所有牛认为是红人,那么其所属的强连通分量内的所有牛都被其他所有牛认为是红人。
由此,我们把图进行强连通分量分解后,至多有一个强连通分量满足题目的条件。
而按前面介绍的算法进行强连通分量分解时,我们还能够得到各个强连通分量拓扑排序后的顺序;
唯一可能成为解的只有拓扑序最后的强连通分量。
所以在最后,我们只要检查这个强连通分量是否从所有顶点可达就好了。
该算法的复杂度为O(N+M),足以在时限内解决原题。
对红色字体的理解:
满足条件的强连通分量的特点是(红牛所在的强连通分量):
(1)出度为0
(2)其余的节点都会间接或直接的指向此强连通分量的任一节点
再结合向量vs 的作用,在Dfs( )中,vs中存储的第一个节点肯定是满足条件的强连通分量中的某一节点;
在vs中,越靠前的节点的拓扑序越大。
AC代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<vector> 5 using namespace std; 6 #define mem(a,b) memset(a,b,sizeof(a)) 7 const int maxn=1e5+50; 8 9 int n,m; 10 int num; 11 int head[maxn]; 12 struct Edge 13 { 14 int to; 15 int next; 16 }G[10*maxn]; 17 void addEdge(int u,int v) 18 { 19 G[num]={v,head[u]}; 20 head[u]=num++; 21 } 22 struct SCC 23 { 24 int col[maxn]; 25 bool vis[maxn]; 26 vector<int >vs; 27 void DFS(int u) 28 { 29 vis[u]=true; 30 for(int i=head[u];~i;i=G[i].next) 31 { 32 int v=G[i].to; 33 if((i&1) || vis[v])//正向边,num为偶数 34 continue; 35 DFS(v); 36 } 37 vs.push_back(u); 38 } 39 void RDFS(int u,int k) 40 { 41 vis[u]=true; 42 col[u]=k; 43 for(int i=head[u];~i;i=G[i].next) 44 { 45 int v=G[i].to; 46 if(!(i&1) || vis[v])//反向边,num为奇数 47 continue; 48 RDFS(v,k); 49 } 50 } 51 int scc() 52 { 53 vs.clear(); 54 mem(vis,false); 55 for(int i=1;i <= n;++i) 56 if(!vis[i]) 57 DFS(i); 58 59 int k=0; 60 mem(vis,false); 61 for(int i=vs.size()-1;i >= 0;--i)//从拓扑序的最大值开始查找SCC 62 if(!vis[vs[i]]) 63 RDFS(vs[i],++k); 64 return k; 65 } 66 }_scc; 67 int Solve() 68 { 69 int k=_scc.scc(); 70 int ans=0; 71 int u; 72 for(int i=1;i <= n;++i) 73 if(_scc.col[i] == k) 74 { 75 ans++; 76 u=i; 77 } 78 mem(_scc.vis,false); 79 _scc.RDFS(u,0);//再次调用RDFS()判断u是否可以到达其他任意节点 80 for(int i=1;i <= n;++i) 81 if(!_scc.vis[i]) 82 return 0; 83 return ans; 84 } 85 void Init() 86 { 87 num=0; 88 mem(head,-1); 89 } 90 int main() 91 { 92 while(~scanf("%d%d",&n,&m)) 93 { 94 Init(); 95 for(int i=1;i <= m;++i) 96 { 97 int u,v; 98 scanf("%d%d",&u,&v); 99 addEdge(u,v); 100 addEdge(v,u); 101 } 102 printf("%d\n",Solve()); 103 } 104 return 0; 105 }