首页 > 代码库 > POJ2186 Popular Cows【Tarjan】【强连通分量】
POJ2186 Popular Cows【Tarjan】【强连通分量】
题目连接:
http://poj.org/problem?id=2186
题目大意:
每头奶牛都希望自己成为最欢迎的那头牛。给你N头牛,M个崇拜关系(A,B)。意思是牛A
崇拜牛B。特别是,如果牛A崇拜牛B,牛B崇拜牛C,那么牛A也崇拜牛C。那么问题来了:
请计算出被所有牛崇拜的牛的个数。
思路:
把崇拜关系(A,B)看做是一条有向边,并且,我们发现牛的崇拜关系具有传递性。那么只要
牛A有一条路径连向牛B,就可以判定牛A崇拜牛B。于是,被所有牛崇拜的牛就是所有的点
都存在一条路径连向它的有向路径。这道题中,将原图强连通分量缩点后构成了DAG,那么
如果新图中出度为0的点只有一个,则有解,为该出度为0的点的强连通分量中点的个数。若
出度为0的点的个数不止一个,那么无解。因为至少有两头牛互相不崇拜。
之前用Kosaraju算法来做,这次用Tarjan算法来做。
描述一下Tarjan算法:
对原图进行优先搜索,每个强连通分量是原图深度优先搜索的子树。那么我们就可以通过确
定每个强连通分量子树的根,根据这些根,从树的最底层开始,一个一个去处强连通分量。
使用两个数组:dfn[v]来表示顶点v被访问的时间,low[v]表示为顶点v邻接的未删除的顶点
u的low[u]和顶点v的low[v]的最小值(low[v]初始化为dfn[v])。这是为了使同一强连通分量的
low[]值都相同,且值与该强连通分量的根的访问时间dfn[]相等,从而就确定了强连通分量
的根和其属于强连通分量的点。
这样,如果发现了low[v] == dnf[v],那么,该点v就是一个强连通分量的根。因为如果该点
v不是强连通分量的根,那么一定属于另一个强连通分量,而且该点v的根就是当前顶点v的祖
宗,那就存在包含当前顶点v到其祖宗的回路,可知顶点v的low[v]值一定是比顶点v的访问时
间dfn[v]更小的值,应为顶点v所在强连通分量的根的dfn[]值。
确定了强连通分量的根后,怎么来取出强连通分量呢?
如果当前节点v为一个强连通分量的根,那么它的强连通分量一定是以该节点v为根节点的子
树。在深度优先遍历的时候维护一个堆栈,每次访问一个新节点,就压入堆栈。由于当前节
点v是这个强连通分量中最先压入堆栈的,那么在当前节点v以后压入堆栈并且仍在堆栈中的
节点都属于这个强连通分量。假设一个节点u在当前节点v压入堆栈后压入并且还存在,同时
节点u不属于该强连通分量。那么一定属于另一个强连通分量,但当前节点v是节点u的根的
祖宗,那么这个强连通分量在之前已经被取出。
具体做法:
(1)访问一个没有被访问过的节点v;否则结束。
(2)初始化dfn[v]和low[v]。
对于节点v的所有邻接顶点u:
1.如果没有访问过,转到步骤(2),同时维护low[v]。
2.如果访问过,但没有删除,维护low[v]。
如果low[v] == dfn[v],那么取出相应的强连通分量。
参考:ACM-ICPC程序设计系列——图论及应用 P115~117
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> using namespace std; const int MAXN = 10010; const int MAXM = 50050; struct EdgeNode { int to; int next; }Edges[MAXM]; int Head[MAXN],vis[MAXN],low[MAXN]; int dfn[MAXN],Stack[MAXN],outdegree[MAXN],Count[MAXN],m,id; void AddEdges(int u,int v) { Edges[id].to = v; Edges[id].next = Head[u]; Head[u] = id++; } int TarBFS(int pos,int lay,int &scc) { vis[pos] = 1; low[pos] = lay; //初始为开始时间 dfn[pos] = lay; //初始为开始时间 Stack[++m] = pos; //将当前未处理节点入栈,回溯时可以判断栈顶到栈中的结点是否为同一个强连通分量 for(int i = Head[pos]; i != -1; i = Edges[i].next) //枚举每一条边 { if(!vis[Edges[i].to]) TarBFS(Edges[i].to,++lay,scc); if(vis[Edges[i].to] == 1) low[pos] = min(low[pos],low[Edges[i].to]); } if(dfn[pos] == low[pos]) //缩点,low[]相同的结点属于同一个强连通分量 { ++scc; do { Count[scc]++; //强连通分量内的点数 low[Stack[m]] = scc; vis[Stack[m]] = 2; }while(Stack[m--] != pos); } return 0; } int Tarjan(int N) { int scc = 0, temp = 0, ans, lay = 1; m = 0; memset(vis,0,sizeof(vis)); memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); for(int i = 1; i <= N; ++i) if(vis[i] == 0) TarBFS(i,lay,scc); for(int i = 1; i <= N; ++i) { for(int j = Head[i]; j != -1; j = Edges[j].next) if(low[i] != low[Edges[j].to]) { outdegree[low[i]] = 1; break; } } for(int i = 1;i <= scc; ++i) { if(! outdegree[i]) { if(++temp > 1) break; ans = Count[i]; } } if(temp != 1) return 0; return ans; } int main() { int N,M,u,v; while(~scanf("%d%d",&N,&M)) { memset(Head,-1,sizeof(Head)); memset(outdegree,0,sizeof(outdegree)); memset(Count,0,sizeof(Count)); id = 0; for(int i = 0; i < M; ++i) { scanf("%d%d",&u,&v); AddEdges(u,v); } int ans = Tarjan(N); printf("%d\n",ans); } return 0; }
POJ2186 Popular Cows【Tarjan】【强连通分量】