强联通分量简讲(Tarjan算法)&&HDU 1269 迷宫城堡

来源:互联网 发布:爱卿网络 编辑:程序博客网 时间:2024/06/11 02:08

Problem Description
为了训练小希的方向感,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”。

本题是强联通分量的模板题,,主要目的是为了学一下强联通分量的基本算法;本题我用的是Tarjan算法,它的思想类似于并查集,主要的操作都是在dfs中完成的,下面我会简单讲讲我的一些理解(由于是自行脑补的成分较多,,所以有bug可以尽管提!)

注意我的实现可能和网上标准的Tarjan算法不太一样,但是用大数据的话会比网上的模板快一点点,不过不知道为什么交到HDU上会比模板慢,,可能是还有一些我没有注意到的技巧。
强联通分量的定义我就不再说了,下面主要说一说它的实现:
Tarjan的实现主体就是dfs。可以自己画一个图感受一下,有向图中,每个强联通分量都是由多个环组成的,且这些环包含了集合中的所有元素且这些环两两相交,那么如果我们对于其中的任意一个元素进行dfs的话,最终这个强连通分量里的元素就会形成一颗子树,并且如果我们把我们的起始元素看作一棵树的根节点的话,那么它的子树中(也就是这个强联通分量中)一定会有节点它的父节点方向连边,并且这些向回连的边最终都会通到根节点,并且叶节点必然会向回连边(这是很显然的)。这样才能满足这个子树中的每个节点都能到达其他的各个节点。
知道了这些性质后,我们就可以进行操作了:
我们将每个强联通分量视为一个集合,一开始所有节点都是各自独立的集合,开两个数组dfn[ ]和low[ ],dfn记录的是节点被访问的顺序,low[i]记录i节点所在集合的代表元素的dfn值(这里类似于并查集),显然对于一个新的节点,集合的代表元素就是它本身,dfn[i]=low[i],。然后我们对其进行dfs,首先将它加入一个栈内,然后对它的每一个可以到达的节点进行dfs,如果它连接的节点是它的一个父节点(或祖父节点,也就是刚刚dfs路径上的点),那么low[i] = min( low[i], low[to] );如果它连接的节点已经被访问过(也就是已经dfs过)并且不是该节点的父节点或祖父节点,直接跳过(因为既然那个节点已经dfs过,而当前节点又是本次才访问到,那么说明那个节点出发一定不能到达当前节点,也就是说它们一定不在一个强联通分量里);我们看看上面这部分操作,它和并查集的操作是很像的,我们相当于将dfs路径上的环合并成了一个集合(相交环也被合成了一个集合),并且代表元素是最先访问的那个点,而由定义可知所有相交环都是一个强联通图,那么我们在回溯时,判断该节点的low[ ]是否等于dfn[ ],如果不相等,那么不做处理,直接返回,如果相等的话就说明该节点是一个集合的代表元素,我们在将刚才栈中的所有在该节点后面入栈的还在栈内的元素(包括它本身)出栈,这些元素就是一个强联通分量。这样的正确性是显然的,我们相当于将所有已经构建的集合保存了下来,而这些集合都是由相交环构成的,而相交环构成的集合一定是一个强联通分量,,所以我们构造出来的集合就一定是原图的强联通分量!
以上就是强联通分量的讲解,,下面让我们来看看这道题:
题目的大意就是让我们判断这些点是否在一个强联通分量里,那么我们就可以以随意的一个节点为起点,向外dfs,如果在回溯过程中发现有一个节点的dfn=low,并且该节点不是我们最初dfs的起点,那么显然所有点构不成一个集合,就不符合题目要求,,如果我们最终求出的一个集合的元素个数少于点的个数,说明还有一部分点与该集合不联通,显然也不符合题目要求,要输出No。排除掉这两种结果后,输出Yes就可以了。由于本题不用统计每个强联通分量的具体元素,所以我就把栈给省了。。

下面是代码:

#include<iostream>#include<climits>#include<cstdio>#include<cstring>#include<algorithm>#define N 10010#define M 100010#define INF INT_MAXusing namespace std;int dfn[N],low[N],now=0,ans,tot,head[N];bool v[N],b[N];     //   v[]标记是否在当前dfs路径上  b[]标记是否访问过 struct Edge { int to,next;Edge() { next=to=-1; } }edge[M];void Add_edge(int a,int b) {    Edge &x=edge[tot];x.to=b,x.next=head[a],head[a]=tot++;}int in() {    char c=0;int s=0;bool v=false;    while(c<'0'||c>'9') c=getchar(),v=c=='-';    while(c>='0'&&c<='9') s=s*10+c-'0',c=getchar();    return v?-s:s;}void input() {    int a=in(),b=in(); Add_edge(a,b);}int dfs(int o) {    if(v[o]) return low[o];if(b[o]) return INF;    v[o]=b[o]=true;low[o]=dfn[o]=now++;    for(int i=head[o];i!=-1;i=edge[i].next) {      low[o]=min(low[o],dfs(edge[i].to));    }    if(low[o]==dfn[o]&&o!=1) ans=-INF; else ans++;    return low[o];}int main() {    while(1) {      int n=in(),m=in();if(n==m&&m==0) break;      memset(head,-1,sizeof(head));memset(v,false,sizeof(v));memset(b,false,sizeof(b));      for(int i=1;i<=m;i++) input();dfs(1);      if(ans==n) puts("Yes");else puts("No");      ans=0;tot=0;now=0;    } return 0;}
1 0
原创粉丝点击