连通性问题——Tarjan
参考自:OI Wik
Tarjan 算法求强连通分量
在 Tarjan 算法中为每个结点x维护了以下几个变量:
- dfn[x]:深度优先搜索遍历时结点x被搜索的次序,搜索到一个节点即入栈,做入栈标记。
- low[x]:在x的子树中能够回溯到的最早的已经在栈中的结点。设以x为根的子树为son[x]。low定义为以下结点的dfn的最小值:son[x]中的结点;从son[x]通过一条不在搜索树上的边能到达的结点。
一个结点的子树内结点的 dfn 都大于该结点的 dfn。
从根开始的一条路径上的 dfn 严格递增,low 严格非降。
按照深度优先搜索算法搜索的次序对图中所有的结点进行搜索,维护每个结点的 dfn 与 low 变量,且让搜索到的结点入栈。每当找到一个强连通元素,就按照该元素包含结点数目让栈中元素出栈。在搜索过程中,对于结点x和与其相邻的结点v(v不是x的父节点)考虑 3 种情况:
- v未被访问:继续对v进行深度搜索。在回溯过程中,用low[v]更新low[x]。因为存在从x到v的直接路径,所以v能够回溯到的已经在栈中的结点,x也一定能够回溯到。
- v被访问过,已经在栈中:根据 low 值的定义,用dfn[v]更新low[x]。
- v被访问过,已不在栈中:说明v已搜索完毕,其所在连通分量已被处理,所以不用对其做操作。
对于一个连通分量图,我们很容易想到,在该连通图中有且仅有一个x使得dfn[x]==low[x]。该结点一定是在深度遍历的过程中,该连通分量中第一个被访问过的结点,因为它的 dfn 和 low 值最小,不会被该连通分量中的其他结点所影响。
因此,在回溯的过程中,判定dfn[x]==low[x]是否成立,如果成立,则栈中x及其上方的结点构成一个 强连通分量,则不停出栈,做出栈标记,直到x出栈。
int dfn[N],low[N],z[N],bh[N],nu[N]; //z[i]:栈;bh[i]:i所属强连通分量的编号;nu[i]:强连通分量i内的节点数; void dfs(int x) { dfn[x]=low[x]=++num; z[++top]=x,pd[x]=1; for(int i=fi[x];i;i=ne[i]) { int v=to[i]; if(!dfn[v]) { dfs(v); low[x]=min(low[x],low[v]); } else if(pd[v]) low[x]=min(low[x],dfn[v]); } if(low[x]==dfn[x]) { int vv=z[top--]; bh[vv]=++cnt; nu[cnt]++; pd[vv]=0; while(vv!=x) { vv=z[top--]; bh[vv]=cnt; nu[cnt]++; pd[vv]=0; } } }
例题:
P2863 [USACO06JAN]The Cow Prom S
#include<bits/stdc++.h> using namespace std; const int N=1e4+5; const int M=5e4+5; int n,m,tot,num,top,ans; int dfn[N],low[N],z[N]; int fi[N],ne[M],to[M]; bool pd[N]; void add(int x,int y) { ne[++tot]=fi[x]; fi[x]=tot; to[tot]=y; } void dfs(int x) { low[x]=dfn[x]=++num; z[++top]=x,pd[x]=1; for(int i=fi[x];i;i=ne[i]) { int v=to[i]; if(!dfn[v]) { dfs(v); low[x]=min(low[x],low[v]); } else if(pd[v]) low[x]=min(low[x],dfn[v]); } if(low[x]==dfn[x]) { int vv=z[top--]; pd[vv]=0; int s=1; while(vv!=x) { vv=z[top--]; s++; pd[vv]=0; } if(s>1) ans++; } } int main() { cin>>n>>m; for(int i=1;i<=m;i++) { int a,b; cin>>a>>b; add(a,b); } for(int i=1;i<=n;i++) if(!dfn[i]) dfs(i); cout<<ans<<'\n'; }
P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G
#include<bits/stdc++.h> using namespace std; const int N=1e4+5; const int M=5e4+5; int n,m,tot,top,num,cnt,ans; int dfn[N],low[N],z[N],bh[N],nu[N]; int fi[N],ne[M],to[M]; bool pd[N],d[N]; void add(int x,int y) { ne[++tot]=fi[x]; fi[x]=tot; to[tot]=y; } void dfs(int x) { dfn[x]=low[x]=++num; z[++top]=x,pd[x]=1; for(int i=fi[x];i;i=ne[i]) { int v=to[i]; if(!dfn[v]) { dfs(v); low[x]=min(low[x],low[v]); } else if(pd[v]) low[x]=min(low[x],dfn[v]); } if(low[x]==dfn[x]) { int vv=z[top--]; bh[vv]=++cnt; nu[cnt]++; pd[vv]=0; while(vv!=x) { vv=z[top--]; bh[vv]=cnt; nu[cnt]++; pd[vv]=0; } } } int main() { cin>>n>>m; for(int i=1;i<=m;i++) { int a,b; cin>>a>>b; add(a,b); } for(int i=1;i<=n;i++) if(!dfn[i]) dfs(i); for(int i=1;i<=n;i++) for(int j=fi[i];j;j=ne[j]) if(bh[i]!=bh[to[j]]) d[bh[i]]=1; int s=0; for(int i=1;i<=cnt;i++) if(!d[i]) s++,ans=i; if(s==1) cout<<nu[ans]<<'\n'; else cout<<'0'<<'\n'; }

浙公网安备 33010602011771号