题干:

为了训练小希的方向感,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; //标记i
dfs(i); //搜索i所能到达的点的个数。
if(cnt<n-1) {
flag=0;
break; //有一个点到其余各点不是n-1时,则该图不联通。
} //退出循环
}
if(flag==1) printf("Yes\n");
else printf("No\n");
}
return 0;
}

新的算法(kosaraju算法):(​​javascript:void(0)​​)

//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");
else
printf("No\n");
}
return 0;
}

其实这题还是可以用并查集做的AC代码:

#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");
else
puts("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!