pku 2186 Popular Cows(强连通分量)

强连通分量的一种求法:

先深搜整个网络中的每个节点,记录每个节点访问结束的顺序(如代码中过的DFS1函数)。

反转图中边的方向。

按节点访问结束从后往前的顺序,深搜,每一棵深搜形成的树就是一个强连通分量(如代码中的DFS2函数)。

 

 

下面说明部分转自http://www.cppblog.com/RyanWang/archive/2009/02/26/74984.aspx,同时也参考了他的代码。

 

题目:n头奶牛,给出若干个欢迎关系a b,表示a欢迎b,欢迎关系是单向的,但是是可以传递的。另外每个奶牛都是欢迎他自己的。求出被所有的奶牛欢迎的奶牛的数目。

算法:对有向图求强连通分量,然后找出所有独立的强连通分量(所谓独立,就是该连通分量里面的点到外面的点没有通路,当然,连通分量外的点是可以有路到强连通分量内的点的),如果独立的强连通分量的数目只有一个,那么,就输出这个强连通分量内解的个数,否则输出无解。

算法证明:
1:假设a和b都是最受欢迎的cow,那么,a欢迎b,而且b欢迎a,于是,a和b是属于同一个连通分量内的点,所有,问题的解集构成一个强连通分量。
2:如果某个强连通分量内的点a到强连通分量外的点b有通路,因为b和a不是同一个强连通分量内的点,所以b到a一定没有通路,那么a不被b欢迎,于是a所在的连通分量一定不是解集的那个连通分量。
3:如果存在两个独立的强连通分量a和b,那么a内的点和b内的点一定不能互相到达,那么,无论是a还是b都不是解集的那个连通分量,问题保证无解。
4:如果图非连通,那么,至少存在两个独立的连通分量,问题一定无解。

#include <iostream> using namespace std; struct Edge { int a,b; }edges[50005]; bool visited[10005];//深搜的时候用,记录节点是否已经被访问 int N,M; int end_order[10005];//记录深搜时候,节点访问结束的顺序 int index; int first[10005];//用于存储图,first[i]指向第一条起始节点为i的边 int belong[10005];//belong[i]记录节点i属于哪一个强连通分量 int cnt[10005];//cnt[i]记录第i个强连通分量中节点的数量 bool is_out[10005];//is_out[i]记录第i个强连通分量是否有出边 int CMP1(const void *a,const void *b)//用于构建前向图,第一次深搜 { return ((Edge*)a)->a-((Edge*)b)->a; } int CMP2(const void *a,const void *b)//反转边的方向,构图,第二次深搜 { return ((Edge*)a)->b-((Edge*)b)->b; } void DFS1(int source) { if(visited[source]) return; visited[source]=true; for(int i=first[source];i<first[source+1];i++) { DFS1(edges[i].b); } end_order[index++]=source;//记录节点访问结束顺序 } void DFS2(int source,int k) { if(visited[source]) return; visited[source]=true; for(int i=first[source];i<first[source+1];i++) { DFS2(edges[i].a,k); } belong[source]=k;//记录节点所属的强连通分量 cnt[k]++;//强连通分量内节点 计数 } int main() { int ans; scanf("%d%d",&N,&M); for(int i=0;i<M;i++) { scanf("%d%d",&edges[i].a,&edges[i].b); edges[i].a--; edges[i].b--; } edges[M].a=edges[M].b=-1;// 简化下面first数组的构建 qsort(edges,M,sizeof(edges[0]),CMP1); int last_source=0;//构建first数组,简化图中边的取用 first[last_source]=0; int k=0; while(last_source<N) { if(edges[k].a==last_source) k++; else first[++last_source]=k; }//构建first数组,简化图中边的取用 memset(visited,0,sizeof(visited)); index=0; for(int i=0;i<N;i++)//第一次深搜 { DFS1(i); } qsort(edges,M,sizeof(edges[0]),CMP2); last_source=0; first[last_source]=0; k=0; while(last_source<N) { if(edges[k].b==last_source) k++; else first[++last_source]=k; } memset(visited,0,sizeof(visited)); memset(cnt,0,sizeof(cnt)); k=0; for(int i=index-1;i>=0;i--) { if(visited[end_order[i]]) continue; DFS2(end_order[i],k);//第二次深搜 k++; } for(int i=0;i<M;i++)//记录哪些强连通分量有出边 { if(belong[edges[i].a]!=belong[edges[i].b]) is_out[belong[edges[i].a]]=true; } int no_out=0; for(int i=0;i<k;i++)//统计无出边的强连通分量的数量 { if(!is_out[i]) { no_out++; ans=cnt[i]; } } if(no_out==1)//输出 printf("%d/n",ans); else printf("0/n"); return 0; } 

你可能感兴趣的:(算法,网络,存储,ini)