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 2156K2: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
此方法是第二种方法的改进,这里用第二个栈代替了方法二中的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
Tarjan_Algorithm比较经典,实现较为简单,空间复杂度较低。
Gabow_Algorithm是Tarjan_Algorithm的改进,时空复杂度是最低的。速度也比较快。
3种算法时间复杂度都是O(v+e)的,Kosaraju_Algorithm常数稍大,空间开销也大,但是有拓扑性质。后2个比较高效,但是不具有拓扑性质。具体看题目情况,合理选择适当算法。