迷宫城堡
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 4794 Accepted Submission(s): 2106
3 3 1 2 2 3 3 1 3 3 1 2 2 3 3 2 0 0
Yes No
题目大意:判断一个图是否是强连通。
题目分析:强连通模版直接上。
求强连通一般有3个算法。
1:Kosaraju_Algorithm
主要方法是先在原图中进行一次dfs,求出每个点离开时间。
然后在反图上根据第一次求出所有顶点离开时间的降序第二次dfs,那么每一次dfs遍历形成的子树就是一个强连通分量。
详情请见代码:
/*Kosaraju_Algorithm*/
#include <iostream>
#include<cstdio>
using namespace std;
const int N = 10005;
int headg[N],headgt[N];
struct edge
{
int to,next;
}g[N<<4],gt[N<<4];
bool flag[N];
int sign[N];//离开时间
int scc[N];//记录连通分量
int n,m,nb;
void dfs1(int cur,int &t)
{
flag[cur] = 1;
for(int i = headg[cur];i != -1;i = g[i].next)
{
if(!flag[g[i].to])
dfs1(g[i].to,t);
}
sign[t ++] = cur;
}
void dfs2(int cur,int sig)
{
scc[cur] = sig;
flag[cur] = 1;
for(int i = headgt[cur];i != -1;i = gt[i].next)
{
if(!flag[gt[i].to])
dfs2(gt[i].to,sig);
}
}
int Kosaraju()
{
int i,sig;
nb = 1;
memset(flag,0,sizeof(flag));
for(i = 1;i <= n;i ++)
{
if(!flag[i])
dfs1(i,nb);
}
memset(flag,0,sizeof(flag));
for(sig = 0,i = n;i >= 1;i --)
{
if(!flag[sign[i]])
dfs2(sign[i],++sig);
}
return sig;
}
int main()
{
int i,j,a,b;
while(scanf("%d%d",&n,&m),(m + n))
{
memset(headg,-1,sizeof(headg));
memset(headgt,-1,sizeof(headgt));
for(i = 1;i <= m;i ++)//建原图和反图
{
scanf("%d%d",&a,&b);
g[i].to = b;
g[i].next = headg[a];
headg[a] = i;
gt[i].to = a;
gt[i].next = headgt[b];
headgt[b] = i;
}
if(Kosaraju() == 1)
printf("Yes\n");
else
printf("No\n");
}
return 0;
}
//15MS 2156K
2:Tarjan_Algorithm
这是一个比较经典的算法,利用标号,直接省掉了Kosaraju_Algorithm的一次dfs,当然也就省掉了一张图。主要思路是直接求出每一个强连通分量的树根,具体做法就是用2个标号数组,index[i]表示访问点i的时间,mlik[i]表示i所在的强连通分量的最先访问时间。当某个点的index值和mlik值相等时说明找到了一个强连通分量的树根,用一个栈维护节点,访问的节点全部入栈,当找到某个树根时,弹出根节点到栈顶的所有元素,这些元素就属于当前节点的一个强连通分量。具体过程可以类比并查集,不断更新的mlik值就相当于找根,同一个强连通的节点的mlik都指向最先访问到这个强连通分量的节点,是为树根。
详情请见代码:
/*Tarjan_Algorithm*/
#include<cstdio>
#include<cstring>
const int N = 10005;
int head[N];
struct edge
{
int to,next;
}g[N<<4];
int flag[N];
int scc[N];
int stack[N];
int mlik[N];
int index[N];
int n,m;
void dfs(int cur,int &t,int &sig)
{
flag[cur] = 1;
stack[++stack[0]] = cur;
index[cur] = mlik[cur] = t ++;
for(int i = head[cur];i != -1;i = g[i].next)
{
if(flag[g[i].to] == 0)
{
dfs(g[i].to,t,sig);
mlik[cur] = mlik[cur] > mlik[g[i].to]?mlik[g[i].to]:mlik[cur];
}
if(flag[g[i].to] == 1)
mlik[cur] = mlik[cur] > index[g[i].to]?index[g[i].to]:mlik[cur];
}
if(index[cur] == mlik[cur])
{
++sig;
do
{
scc[stack[stack[0]]] = sig;
flag[stack[stack[0]]] = 2;
}while(stack[stack[0] --] != cur);
}
}
int Tarjan()
{
int i,sig,t;
sig = 0;
t = 1;
stack[0] = 0;
memset(flag,0,sizeof(flag));
for(i = 1;i <= n;i ++)
{
if(flag[i] == 0)
dfs(i,t,sig);
}
return sig;
}
int main()
{
int i,j,a,b;
while(scanf("%d%d",&n,&m),(n + m))
{
memset(head,-1,sizeof(head));
for(i = 1;i <= m;i ++)
{
scanf("%d%d",&a,&b);
g[i].to = b;
g[i].next = head[a];
head[a] = i;
}
if(Tarjan() == 1)
printf("Yes\n");
else
printf("No\n");
}
return 0;
}
//31MS 1508K
3:Gabow_Algorithm
此方法是第二种方法的改进,这里用第二个栈代替了方法二中的2个标号数组,辅助求强连通分量的根。改进了方法二中的频繁更新标号数组的缺点。
详情请见代码:
/*Gabow_Algorithm*/
#include<cstdio>
#include<cstring>
const int N = 10005;
int head[N];
int scc[N];
int stack1[N];
int stack2[N];
int in[N];
struct edge
{
int to,next;
}g[N<<4];
int n,m;
void dfs(int cur,int &sig,int &ans)
{
in[cur] = ++ sig;
stack1[++stack1[0]] = cur;
stack2[++stack2[0]] = cur;
for(int i = head[cur];i != -1;i = g[i].next)
{
if(in[g[i].to] == 0)
dfs(g[i].to,sig,ans);
else
if(scc[g[i].to] == 0)
{
while(in[stack2[stack2[0]]] > in[g[i].to])
stack2[0] --;
}
}
if(stack2[stack2[0]] == cur)
{
stack2[0] --;
ans ++;
do
{
scc[stack1[stack1[0]]] = ans;
}while(stack1[stack1[0]--] != cur);
}
}
int Gabow()
{
int i,sig,ret;
memset(in,0,sizeof(in));
memset(scc,0,sizeof(scc));
stack1[0] = stack2[0] = sig = ret = 0;
for(i = 1;i <= n;i ++)
if(!in[i])
dfs(i,sig,ret);
return ret;
}
int nextint()
{
char c;
int ret;
while((c = getchar()) > '9' || c < '0')
;
ret = c - '0';
while((c = getchar()) >= '0' && c <= '9')
ret = ret * 10 + c - '0';
return ret;
}
int main()
{
int i,j,a,b;
while(scanf("%d%d",&n,&m),(n + m))
{
memset(head,-1,sizeof(head));
for(i = 1;i <= m;i ++)
{
//scanf("%d%d",&a,&b);
a = nextint();
b = nextint();
g[i].to = b;
g[i].next = head[a];
head[a] = i;
}
if(Gabow() == 1)
printf("Yes\n");
else
printf("No\n");
}
return 0;
}
//0MS 1400K
三种方法的比较
Kosaraju_Algorithm理论复杂度是最高的,空间复杂度也很高,因为要建两张图。但是求出的强连通分量隐藏了一个比较好的性质:是一个拓扑序列。
Tarjan_Algorithm比较经典,实现较为简单,空间复杂度较低。
Gabow_Algorithm是Tarjan_Algorithm的改进,时空复杂度是最低的。速度也比较快。
3种算法时间复杂度都是O(v+e)的,Kosaraju_Algorithm常数稍大,空间开销也大,但是有拓扑性质。后2个比较高效,但是不具有拓扑性质。具体看题目情况,合理选择适当算法。