【图论复习】Tarjan 算法(Tarjan LCA 除外)
好久没写 Tarjan,反正也快 CSP 了,赶紧复习一下。
Tarjan 就是基于 dfs 树中的 dfs 序 以及 low 数组来进行搜索,注意不同的算法 low 的更新时不一样的,其他的感觉没什么好讲的,基本上可以说是背代码的吧。
复杂度都是 \(\Theta(n+m)\)。
强连通分量
对于一个有向图 \(G=\{V,E\}\),存在点集一个的子集,使得这个子集中任意两个点都可以互相到达,那么这个子集被称为这张图的连联通分量。联通分量的极大值被称为强联通分量。
Tips:
注意需要另外开一个 vis 数组代表这个节点是否在栈内,如果在栈内才能更新 low。
void dfs(int x){
dfn[x]=low[x]=++cnt; vis[x]=1; st[++top]=x; int i;
for(i=head[x];i;i=nex[i]){
if(!dfn[to[i]]) dfs(to[i]);
if(vis[to[i]]) low[x]=mmin(low[x],low[to[i]]);
}
if(low[x]==dfn[x]){
num++; scc[x]=num; sum[num]=c[x]; vis[x]=0;
while(st[top]!=x) scc[st[top]]=num,vis[st[top]]=0,sum[num]+=c[st[top]],top--; top--;
} return;
}
割点
对于一个无向图,如果一个点删除之后,这张图其他点的连通性改变,那么这个点被称之为割点。
Tips:
如果接下来的节点之前访问过,那么只能用下一个节点的 dfn 更新当前点的 low 而非是当前下一个点的 low。
注意判断是不是割点是 low[to[i]]>=dfn[x]
(带等号),而且需要特判根。
void dfs(int x,int fa){
dfn[x]=low[x]=++cnt; int i,d=0;
for(i=head[x];i;i=nex[i]) if(!dfn[to[i]]){
d++; dfs(to[i],fa); low[x]=mmin(low[x],low[to[i]]);
if(low[to[i]]>=dfn[x]&&x!=fa) flag[x]=1;
} else low[x]=mmin(low[x],dfn[to[i]]);
if(x==fa&&d>1) flag[x]=1; return;
}
割边
对于一个无向图,如果一条边删除之后,这张图其他点的连通性改变,那么这个边被称之为割边,又称作桥。
Tips:
更新 low 和割点一样。
注意判断是不是割边是 low[to[i]]>dfn[x]
(不带等号)。flag[x] 代表 x 和 fa[x] 之间的边是不是割边。
注意重边。
void dfs(int x,int pre){
dfn[x]=low[x]=++cnt; int i,d=0;
for(i=head[x];i;i=nex[i]) if(!dfn[to[i]]){
d++; fa[to[i]]=x; dfs(to[i],i); low[x]=mmin(low[x],low[to[i]]);
if(low[to[i]]>dfn[x]) flag[x]=1;
} else if(i!=(pre^1)) low[x]=mmin(low[x],dfn[to[i]]);
return;
}
点双
对于一个无向图,如果一个极大子图没有一个点是割点,那么这个子图被称为点双连通分量。
Tips:
更新 low 和割点一样。
加入点双条件和割点一样(带等于)。
注意退栈的时候是退到 to[i](要退),当前节点不需要退栈但是需要加入点双(所以存在一些点在多个点双内)
void dfs(int x,int fa){
dfn[x]=low[x]=++cnt; st[++top]=x; int i,d=0;
for(i=head[x];i;i=nex[i]) if(!dfn[to[i]]){
d++; dfs(to[i],x); low[x]=mmin(low[x],low[to[i]]);
if(low[to[i]]>=dfn[x]){
num++; ans[num].push_back(x);
while(st[top+1]!=to[i]) ans[num].push_back(st[top]),top--;
}
} else if(to[i]!=fa) low[x]=mmin(low[x],dfn[to[i]]);
if(fa==0&&d==0) ans[++num].push_back(x); return;
}
边双
对于一个无向图,如果一个极大子图没有一个边是割边,那么这个子图被称为边双连通分量。
Tips:
注意更新 low 和割点一样。
注意加入边双的条件和强连通分量一样。
void dfs(int x,int pre){
dfn[x]=low[x]=++cnt; st[++top]=x; int i,d=0;
for(i=head[x];i;i=nex[i]) if(!dfn[to[i]]){
d++; dfs(to[i],i); low[x]=mmin(low[x],low[to[i]]);
} else if(i!=(pre^1)) low[x]=mmin(low[x],dfn[to[i]]);
if(dfn[x]==low[x]){
++num;
while(st[top]!=x) ans[num].push_back(st[top]),top--;
ans[num].push_back(st[top]),top--;
} return;
}