[笔记] Tarjan 全家桶
Tarjan 全家桶
算法概念
Tarjan 是一种基于深度优先搜索(DFS)的线性算法,用于求解图的连通性相关问题。
算法原理
Tarjan 基于 DFS 生成树,以下是几个重要概念:
顾名思义,DFS 生成树 就是在图上做 DFS 搜索,以搜索到的顺序为准将图转化为一棵树。
如图所示,原图的边被分为了四种:
-
树边:示意图中以黑色边表示,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边。
-
反祖边:示意图中以红色边表示(即 7
\rightarrow 1),也被叫做回边,即指向祖先结点的边。 -
横叉边 (仅在有向图中出现):示意图中以蓝色边表示(即 9
\rightarrow 7),它主要是在搜索的时候遇到了一个已经访问过的结点,但是这个结点并不是当前结点的祖先。 -
前向边:示意图中以绿色边表示(即 3
\rightarrow 6),它是在搜索的时候遇到子树中的结点的时候形成的。
其中在 Tarjan 算法中最重要的是树边和返祖边。
在 Tarjan 算法中最重要的是维护两个值:
算法实现
下面给出维护这两个值的代码,后面的所有应用都基于这段代码
int dfn[N],low[N],tot;
void dfs(int u){
dfn[u]=low[u]=++tot;
for(int i=fir[u];i;i=e[i].nex){
int v=e[i].v;
if(!dfn[v]){
dfs(v);
low[u]=min(low[u],low[v]);//通过u子树中的某条返祖边来更新low
}else low[u]=min(low[u],dfn[v]);//通过u的返祖边更新low
}
}
强连通分量(SCC)
概念
强连通指的是在一张有向图中,任意两个结点连通。 而强连通分量是一个极大的强连通子图。注意这里的极大并不是指子图的规模大小很大,而是指这个子图要尽可能大。\ 用数学语言来描述就是:
一个强连通子图
G' = (V', E') ,\ 其中V' \subseteq V ,E' \subseteq E 。\当且仅当不存在包含 $G'$ 的更大子图 $G'' = (V'', E'')$,满足 $V' \subseteq V'' \subseteq V$,$E' \subseteq E'' \subseteq E$。
算法原理
如果结点
反证法:
假设有个结点
实现
我们用一个栈来实现,当某个节点
code
int dfn[N],low[N],top,tot,belong[N],in[N],res;
/*
# belong[N] 记录每个节点属于哪个强连通分量
*/
int sta[N];
vector<int> scc[N];
void dfs(int u){
dfn[u]=low[u]=++tot;
sta[++top]=u;
in[u]=1;
for(int i=fir[u];i;i=e[i].nex){
int v=e[i].v;
if(!dfn[v]){
dfs(v);
low[u]=min(low[u],low[v]);
}else if(in[v])low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
++res;
scc[res].push_back(u);
while(sta[top]!=u){
belong[sta[top]]=res;
in[sta[top]]=0;
scc[res].push_back(sta[top]);
--top;
}
--top;
in[u]=0;
belong[u]=res;
}
}
例题
B3609 [图论与代数结构 701] 强连通分量
割点
概念
对于一个无向图,如果把一个点删除后这个图的极大连通分量数增加了,那么这个点就是这个图的割点(又称割顶)。
通俗一点就是在原图中本来连通的两点在点
算法原理
那么怎么用 Tarjan 求割点呢?还是用到
设节点
因为
此处有一个例外,设
code
int cut[N];
int dfn[N],low[N],tot;
void dfs(int u,int root){
int son=0;
dfn[u]=low[u]=++tot;
for(int i=fir[u];i;i=e[i].nex){
int v=e[i].v;
if(!dfn[v]){
dfs(v,0);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]&&!root)cut[u]=1;
if(root)++son;
}else low[u]=min(low[u],dfn[v]);
}
if(root&&son>=2)cut[u]=1;
}
例题
P3388【模板】割点(割顶)
P3469 [POI2008] BLO-Blockade
点双连通分量(BCC)
概念
在一张连通的无向图中,对于两个点
u 和v ,如果无论删去哪个点(只能删去一个,且不能删u 和v 自己)都不能使它们不连通,我们就说u 和v 点双连通。\ 对于一个无向图中的 极大 点双连通的子图,我们称这个子图为一个 点双连通分量。
通俗的讲,点双连通分量就是在该子图
算法原理
首先需要了解一个性质:
- 对于一个点双连通分量,它在 DFS 搜索树中
dfn 值最小的点一定是割点或者树根。
我们根据这个性质分类讨论
- 当这个点为割点时,它一定是点双连通分量的根,因为一旦包含它的父节点,他仍然是割点。
- 当这个点为树根时:\ a. 有两个及以上子树,它是一个割点。\ b. 只有一个子树,它是一个点双连通分量的根。\ c. 它没有子树,视作一个点双。
我们用一个栈来维护,当满足
code
int dfn[N],low[N],tot,top,res;
int sta[N];
vector<int> bcc[N];
void dfs(int u,int root){
int son=0;
dfn[u]=low[u]=++tot;
sta[++top]=u;
if(root&&!fir[u]){
bcc[++res].push_back(u);
return ;
}
for(int i=fir[u];i;i=e[i].nex){
int v=e[i].v;
if(!dfn[v]){
dfs(v,0);
low[u]=min(low[u],low[v]);
if(dfn[u]<=low[v]){
res++;
while(sta[top]!=v){
bcc[res].push_back(sta[top]);
--top;
}
bcc[res].push_back(sta[top]);
--top;
bcc[res].push_back(u);
}
}else low[u]=min(low[u],dfn[v]);
}
}
性质
对于一个点双中的两点,它们之间简单路径的并集,恰好完全等于这个点双。
对于在同一个点双内的
例题
P8435【模板】点双连通分量
割边 (桥)
概念
对于一个无向图,如果删掉一条边后图中的连通分量数增加了,则称这条边为桥或者割边。\ 严谨来说,就是:假设有连通图
G=\{V,E\} ,e 是其中一条边(即e \in E ),如果G-e 是不连通的,则边e 是图G 的一条割边(桥)。
通俗的讲,本来连通的两点在删去一条边后变得不连通了,那么这条边就是一条割边(桥)。
算法原理
设节点
其实跟割点差不多,原理不再赘述,而且不用考虑根节点的问题,至于为什么留给读者自行思考。
code
int n,m,dfn[N],low[N],cut[M],tot;
void dfs(int u,int in_E){
dfn[u]=low[u]=++tot;
for(int i=fir[u];i;i=e[i].nex){
int v=e[i].v;
if(!dfn[v]){
dfs(v,i);
if(dfn[u]<low[v])cut[i]=cut[i^1]=1;//双向边两条同时标记为割边
low[u]=min(low[u],low[v]);
}else if(i!=(in_E^1))low[u]=min(low[u],dfn[v]);
}
}
边双连通分量(DCC)
概念
在一张连通的无向图中,对于两个点
u 和v ,如果无论删去哪条边(只能删去一条)都不能使它们不连通,我们就说u 和v 边双连通。\ 对于一个无向图中的 极大 边双连通的子图,我们称这个子图为一个 边双连通分量。
通俗的讲,边双连通分量就是在该子图
算法原理
边双连通分量比较简单,先求出所有割边,在将割边全部删去,剩下的每一个连通块都是一个边双连通分量
code
int n,m,dfn[N],low[N],cut[M],tot,res,belong[N];
vector<int> dcc[N];
void dfs(int u,int in_E){
dfn[u]=low[u]=++tot;
for(int i=fir[u];i;i=e[i].nex){
int v=e[i].v;
if(!dfn[v]){
dfs(v,i);
if(dfn[u]<low[v])cut[i]=cut[i^1]=1;//双向边两条同时标记为割边
low[u]=min(low[u],low[v]);
}else if(i!=(in_E^1))low[u]=min(low[u],dfn[v]);
}
}
void dfs2(int u){;
belong[u]=res;
Ans[res].push_back(u);
for(int i=fir[u];i;i=e[i].nex){
int v=e[i].v;
if(belong[v]||b[i])continue;
dfs2(v);
}
}
例题
P8436 【模板】边双连通分量
缩点
概念
在一个图上将所有的 强连通分量 / 点双连通分量 / 边双连通分量 缩成一个点来处理,将整个图转化为一个 DAG / 树,然后再 搜索 / DP 。
例题
P4306 [JSOI2010] 连通数
P4819 [中山市选] 杀人游戏
P8867 [NOIP2022] 建造军营
圆方树
概念
树有很多很好的性质,并且便于用一些数据结构维护信息,圆方树是一种可以将一般无向图转化为树,并在树上使用一些树上技巧/数据结构来处理问题的算法。
其实圆方树最开始是处理一些仙人掌上问题的工具,后来经过一些修改使得可以在一般无向图上使用。
算法原理
先讲处理仙人掌的圆方树,将每个环建一个方点连接环上所有点,然后将这个环断开。