<题目链接>
题目大意:
为了训练小希的方向感,Gardon建立了一座大城堡,里面有N个房间(N<=10000)和M条通道(M<=100000),每个通道都是单向的,就是说若称某通道连通了A房间和B房间,只说明可以通过这个通道由A房间到达B房间,但并不说明通过它可以由B房间到达A房间。Gardon需要请你写个程序确认一下是否任意两个房间都是相互连通的,即:对于任意的i和j,至少存在一条路径可以从房间i到房间j,也存在一条路径可以从房间j到房间i。
Input
输入包含多组数据,输入的第一行有两个数:N和M,接下来的M行每行有两个数a和b,表示了一条通道可以从A房间来到B房间。文件最后以两个0结束。
Output
对于输入的每组数据,如果任意两个房间都是相互连接的,输出"Yes",否则输出"No"。
Sample Input
3 3
1 2
2 3
3 1
3 3
1 2
2 3
3 2
0 0
Sample Output
Yes
No
解题分析:
有向图求强连通分量的裸题,下面用的是Tarjan算法。
1 #include <cstdio> 2 #include <cstring> 3 4 const int M=100000+5; 5 int n,m,cnt,tot,sumlink,top; 6 int dfn[M],Stack[M],head[M],low[M]; 7 bool vis[M]; 8 struct DEGE{ 9 int to,next; 10 }edge[M]; 11 void init(){ 12 memset(vis,0,sizeof(vis)); 13 memset(dfn,0,sizeof(dfn)); 14 memset(head,-1,sizeof(head)); 15 cnt=tot=sumlink=top=0; 16 //cnt为edge[]中边的编号,tot为dfs遍历到的时间,sumlink为连通分量的数量,top为栈顶元素序号 17 } 18 int min(int a,int b){return a<b?a:b;} 19 void add(int u,int v){ //链式前向星存图 20 edge[++cnt].to=v,edge[cnt].next=head[u]; 21 head[u]=cnt; 22 } 23 void Tarjan(int u){ 24 if(sumlink>1)return; //如果连通分量>1,说明不符合题意,直接结束 25 dfn[u]=low[u]=++tot; //dfn为遍历的序号 26 Stack[++top]=u;//入栈 27 vis[u]=true; //标记该点在栈里 28 for(int i=head[u];~i;i=edge[i].next){ 29 int v=edge[i].to; 30 if(!dfn[v]){//如果这个点没有遍历过 31 Tarjan(v); 32 low[u]=min(low[u],low[v]);//将每个点的low值与根节点的low[]值相同 33 } 34 else if(vis[v])low[u]=min(low[u],dfn[v]); 35 //如果这个点在栈中,即这个联通块产生A->B->C->A的时候,那么这些元素相互可达,就将它们的low[]值置为根的dfn值(该连通块最先到达的点,即此处的dfn[]) 36 } 37 if(dfn[u]==low[u]){//如果找到了这个连通分量的根节点 38 sumlink++;//连通分量数量+1 39 while(true){ //根据上面连通块的遍历过程来看,连通块中的所有点,必然在栈中是一块连续区域 40 int temp=Stack[top]; 41 vis[temp]=false; //temp出栈,去除在栈内的标记 42 --top; //将该连通块在栈中的点排出 43 if(temp==u)break;//将该连通块中的根节点搜索完后(根节点==该连通块最早被dfs到的点==该连通块在栈的最底部的点) 44 } 45 } 46 } 47 int main(){ 48 while(scanf("%d%d",&n,&m)!=EOF,n||m){ 49 init(); 50 for(int i=1;i<=m;i++){ 51 int u,v; 52 scanf("%d%d",&u,&v); 53 add(u,v); 54 } 55 for(int i=1;i<=n;i++){ 56 if(!dfn[i]) 57 Tarjan(i); 58 } 59 if(sumlink==1)printf("Yes "); 60 else printf("No "); 61 } 62 return 0; 63 }
2018-08-16