题干:
为了训练小希的方向感,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算法看是否只有一个强连通分量。
AC代码:
#include<cstdio>
#include<stack>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int MAXN = 10000 + 5;
const int MAXM = 100000 + 5;
int n,m;
int top,tot,cnt;
int head[MAXN];
bool vis[MAXN];
int dfn[MAXN],low[MAXN];
stack<int> sk;
struct Edge {int to,ne;
}e[MAXM];
void add(int u,int v) {e[++top].to = v;e[top].ne = head[u];head[u] = top;
}
void tarjan(int x) {low[x] = dfn[x] = ++tot;sk.push(x);vis[x] = 1;for(int i = head[x]; i!=-1; i=e[i].ne) {int v = e[i].to;if(dfn[v] == 0) {tarjan(v);low[x] = min(low[x],low[v]);}else if(vis[v] == 1) {low[x] = min(low[x],dfn[v]);}}if(low[x] == dfn[x]) {cnt++;while(1) {int now = sk.top();sk.pop();vis[now]=0;if(now == x) break;}}}
int main()
{int a,b;while(~scanf("%d%d",&n,&m)) {if(n == 0 && m == 0) break;top = tot = cnt = 0;while(sk.size()) sk.pop();memset(head,-1,sizeof head);memset(vis,0,sizeof vis);for(int i = 1; i<=n; i++) dfn[i] = low[i] = 0;for(int i = 1; i<=m; i++) {scanf("%d%d",&a,&b);add(a,b);}for(int i = 1; i<=n; i++) {if(dfn[i] == 0) tarjan(i);}if(cnt == 1) puts("Yes");else puts("No");}return 0 ;
}
直接暴力AC代码2:
//hdu1269(迷宫城堡&&判断有向图是否联通)
//题目大意:已知有n(n<=10000)个点,M(M<=100000)个道路。每条道路都是单向的。求任意两个点之间是否联通?
//解题思路:由于点n的范围较大,所以用邻接表存储n个点的道路情况。然后依次搜索n各点能否
//到达剩余的n-1个点。如果有一个不满足,就退出循环。表示该有向图不是联通图。否则就是。
//这里我用的是dfs搜索看是否某个点能否到达其余的n-1个点。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,head[100010],k,flag,vis[10010],cnt,gg;
struct node {int x,y;int next;
} edge[100010];
void init() { //初始化邻接表k=0;memset(head,-1,sizeof(head));
}
void input() { //读取数据init();int i,j,x,y;for(i=0; i<m; i++) {scanf("%d%d",&x,&y);edge[k].x=x;edge[k].y=y;edge[k].next=head[x]; //建x到y的边。head[x]=k++;}
}
void dfs(int x) {int v,i;if(cnt==n-1) {//可以到达其余的n-1个点gg=1;return; } for(i=head[x]; i!=-1; i=edge[i].next) {v=edge[i].y;if(!vis[v]) {vis[v]=1;cnt++; //cnt用来记录从某点出发所能到达的点数dfs(v);if(gg) return;}}
}
int main() {int i,j;while(scanf("%d%d",&n,&m)&&(n|m)) {input();flag=1;for(i=1; i<=n; i++) {cnt=0;gg=0;memset(vis,0,sizeof(vis));vis[i]=1; //标记idfs(i); //搜索i所能到达的点的个数。if(cnt<n-1) {flag=0;break; //有一个点到其余各点不是n-1时,则该图不联通。} //退出循环}if(flag==1) printf("Yes\n");else printf("No\n");}return 0;
}
新的算法(kosaraju算法):(https://blog.csdn.net/qq7366020/article/details/12943345)
//Final kosaraju判断有向图是否为强联通图
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX 10100
struct snode{int to,next;
}edge1[MAX*30],edge2[MAX*30],edge3[MAX*30];
int visit1[MAX],In[MAX],To[MAX],pre1[MAX],pre2[MAX],Index1,Index2,k,list[MAX];
int father[MAX];
void Add_edge1(int a,int b) //建立正向图
{edge1[Index1].to=b,edge1[Index1].next=pre1[a];pre1[a]=Index1++;
}void Add_edge2(int a,int b) //建立逆向图
{edge2[Index2].to=b,edge2[Index2].next=pre2[a];pre2[a]=Index2++;
}void Kosaraju(int u) //第一次正向图搜索
{int i,v;for(i=pre1[u];i!=-1;i=edge1[i].next){v=edge1[i].to;if(visit1[v]==0){visit1[v]=1;Kosaraju(v);}}list[k++]=u;
}void DFS(int u,int Father) //第二次逆向图搜索
{int i,v;visit1[u]=2;father[u]=Father;for(i=pre2[u];i!=-1;i=edge2[i].next){v=edge2[i].to;if(visit1[v]==1)DFS(v,Father);}
}int main()
{int n,m,i,j,a,b,c,pd;while(scanf("%d%d",&n,&m)!=EOF){if(n==0&&m==0)break;Index1=Index2=0;memset(In,0,sizeof(In));memset(pre1,-1,sizeof(pre1));memset(pre2,-1,sizeof(pre2));memset(visit1,0,sizeof(visit1));memset(father,0,sizeof(father));for(i=0;i<m;i++){scanf("%d%d",&a,&b);Add_edge1(a,b);Add_edge2(b,a);}for(i=1,k=0;i<=n;i++){if(!visit1[i]){visit1[i]=1;Kosaraju(i);}}for(j=k-1,c=0;j>=0;j--) //第二次搜索和第一次搜索顶点的顺序一样{if(visit1[list[j]]==1){DFS(list[j],++c);}}for(i=1,pd=0;i<n;i++) //有两个联通块就说明不是联通图if(father[i]!=father[i+1])pd=1;if(pd==0)printf("Yes\n");elseprintf("No\n");}return 0;
}
其实这题还是可以用并查集做的AC代码:(https://blog.csdn.net/wyjwyl/article/details/49369085)
#include<cstdio>
#include<cstring>
#define maxn 10000+10
int rec[3][maxn];
int n,m;
void init()
{for(int i=1;i<=2;++i){for(int j=1;j<=n;++j)rec[i][j]=j;}
}
int find(int x,int i)
{return x==rec[i][x]?x:rec[i][x]=find(rec[i][x],i);
}
void merge(int a,int b)
{if(a!=n){int fa=find(a,1);int fb=find(b,1);if(fa!=fb)rec[1][a]=b;//合并到后面的那个元素身上}if(b!=n){int fa=find(a,2);int fb=find(b,2);if(fa!=fb)rec[2][b]=a;//合并到前面的那个元素上}
}
int main()
{int a,b;while(scanf("%d%d",&n,&m)&&(n||m)){int flag=1;init();for(int i=1;i<=m;++i){scanf("%d%d",&a,&b);merge(a,b);}for(int i=1;i<=n;++i){if(find(i,1)!=n||find(i,2)!=n){flag=0;break;}}if(flag)puts("Yes");elseputs("No");}return 0;
}
超时代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int MAXN = 10000 + 5;
const int MAXM = 100000 + 5;
int n,m;
int top,flag;
int head[MAXN];
bool vis[MAXN];
struct Edge {int to,ne;}e[MAXM];
void add(int u,int v) {e[++top].to = v;e[top].ne = head[u];head[u] = top;
}
void dfs(int x,int cnt) {if(x == 1 && cnt == n+1) {
// for(int i = head[x]; i!=-1; i=e[i].ne) {
// if(e[i].to == 1) {
// flag =1;break;
// }
// }flag=1;return ;}for(int i = head[x]; i!=-1; i=e[i].ne) {if(vis[e[i].to] && e[i].to != 1) continue;vis[e[i].to]=1;dfs(e[i].to,cnt+1);if(flag) return;vis[e[i].to] = 0;}
}
int main()
{int a,b;while(~scanf("%d%d",&n,&m)) {if(n == 0 && m == 0) break;top = flag = 0;memset(head,-1,sizeof head);memset(vis,0,sizeof vis);for(int i = 1; i<=m; i++) {scanf("%d%d",&a,&b);add(a,b);}dfs(1,1);if(flag) puts("Yes");else puts("No");}return 0 ;
}
总结: 这件事情告诉我们一个道理。。。。不带回溯的dfs要比带回溯的dfs快的多的多的多,因为不带回溯的是一遍on,带回溯的是n!