Tarjan 系列学习笔记
最近在复习提高算法,所以学习复习笔记写的就比较多。
Tarjan 系列的算法主要针对于图论而言。
Part \(1\) 缩点
缩点算是 Tarjan 算法最广泛的应用了。
先讲拓扑序。在一个有向图中,若此图无环,我们称这个图是有向无环图,也叫 DAG,我们可以用拓扑排序解决许多图上问题,简单思路是先把入度为 \(0\) 的点丢进队列,再像 spfa 一样向四周扩散,每扩散一次就做对应操作(这里的操作是对于题目而言的),将扩散到的所有点的入度都减 \(1\),直到某一个点入度变为 \(0\) 后再丢进队列,反复循环即可。
拓扑排序代码:
点击查看代码
while(!q.empty()){
a=q.front(),q.pop();
for(int y:G[a]){
p[y]+=p[a];//这里是你要做的操作
ru[y]--;
if(ru[y]==0) q.push(y);
}
}
但是更多时候有向图是有环的,那怎么办呢?现在就引进缩点这一概念。
先给出一些定义:
-
在有向图中,若点 \(u\) 和点 \(v\) 能相互到达,那么称点 \(u\) 与点 \(v\) 是强连通的。
-
在一个有向图中,若任意点对都是强连通的,那么称这个图是个强连通图。
-
在一个有向图中,极大的强连通子图称为是这个有向图的强连通分量。对于“极大”这一理解,就是任意加一个点 \(u\),这个子图都无法构成强连通图。
那么 Tarjan 算法在这里的应用,就是把所有强连通分量缩成一个点,缩完后的新图是一个 DAG,相当于在缩点过程中把环都缩掉了。缩完点之后我们就可以拓扑排序解决我们需要解决的问题了。
下面讲如何实现 Tarjan 缩点。
首先是几个定义:定义 \(dfn_{i}\) 表示点 \(i\) 的时间戳;\(low_{i}\) 表示点 \(i\) 能到的最小时间戳;\(vis_{i}\) 表示点 \(i\) 的访问情况:\(vis_{i}=0\) 表示未访问,\(vis_{i}=1\) 表示已访问但不知道属于哪个强连通分量,\(vis_{i}=2\) 表示已经知道属于哪个强连通分量。
先想想如何得到 \(low\) 数组。一开始,\(low_{i}\) 肯定等于 \(dfn_{i}\),对于其后继节点 \(u \in son_{i}\),可能通过某些返祖边回到点 \(i\) 的祖先,所以我们有 \(low_{u}=\min\limits_{v\in son_{u}}\{low_{v}\}\)。
首先容易理解的是,对原图 dfs 后会得到一棵搜索树(或多棵),我们不断把每个点加入栈中,考虑 \(dfn_{i}=low_{i}\) 的含义,即点 \(i\) 无法到达比他更小的时间戳,那么显然点 \(i\) 是某个强连通分量的根节点(对于搜索树而言),相当于是当前的强连通分量搜了一圈后又回到了根节点,这时我们就可以把从栈顶到点 \(i\) 的所有点出栈,这些点即构成一个强连通分量,用一个 \(bel\) 数组记录每个点所属的强连通分量编号即可。缩完点后记得重新建图。
缩点代码:
点击查看代码
void Tarjan(int a){
dfn[a]=low[a]=++num,st[++s]=a,vis[a]=1;
for(int y:G[a]){
if(vis[y]==2) continue;
if(vis[y]==0) Tarjan(y);
low[a]=min(low[a],low[y]);
}
if(dfn[a]==low[a]){
cnt++;
while(st[s]!=a) vis[st[s]]=2,del[st[s]]=cnt,dis[cnt]++,s--;
vis[st[s]]=2,del[st[s]]=cnt,dis[cnt]++,s--,p[cnt]=dis[cnt];
}
}
Part \(2\) 点双·边双
定义:在无向图中,若删除点 \(u\) 后图不连通,则点 \(u\) 为割点;同理,若删除 \((u,v)\) 这条边后图不连通,则 \((u,v)\) 为割边。点双连通分量不存在割点,边双连通分量不存在割边。
若某个点是割点,则其后代一定不能通过除这个点之外的点回到 \(dfn\) 更小的点去,最多回到这个点,所以如果存在 \(low_{v} \ge dfn_{u}\),则点 \(u\) 是割点。特别的,若 \(u\) 是搜索树的根节点,则一定有 \(low_{v} \ge dfn_{u}\),所以判断条件必须改成判断子树个数。复杂度 \(O(n)\),代码如下:
点击查看代码
void Tarjan(int x,int lst){
dfn[x]=low[x]=++num;
int cld=0;
for(int y:G[x]){
if(y==lst) continue;
if(!dfn[y]){
Tarjan(y,x),low[x]=min(low[x],low[y]),cld++;
if(low[y]>=dfn[x] && !lst) f[x]=1;
}
else low[x]=min(low[x],dfn[y]);
}
if(!lst && cld>=2) f[x]=1;
}
割边类似,判断 \(low_{v} > dfn_{x}\) 即可。代码:
点击查看代码
void Tarjan(int x,int lst){
dfn[x]=low[x]=++num;
int fl=0;
for(int y:G[x]){
if(y==lst && !fl){fl=1;continue;}
if(!dfn[y]){
Tarjan(y,x),low[x]=min(low[x],low[y]);
if(low[y]>dfn[x]) f[make_pair(x,y)]=1;
}
else low[x]=min(low[x],dfn[y]);
}
}

浙公网安备 33010602011771号