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 }
View Code

 

posted @ 2018-10-03 20:11  HHHyacinth  阅读(343)  评论(0编辑  收藏  举报