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);
	}
}

但是更多时候有向图是有环的,那怎么办呢?现在就引进缩点这一概念。

先给出一些定义:

  1. 在有向图中,若点 \(u\) 和点 \(v\) 能相互到达,那么称点 \(u\) 与点 \(v\) 是强连通的。

  2. 在一个有向图中,若任意点对都是强连通的,那么称这个图是个强连通图。

  3. 在一个有向图中,极大的强连通子图称为是这个有向图的强连通分量。对于“极大”这一理解,就是任意加一个点 \(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]);
	} 
} 
posted @ 2023-08-06 14:07  Nwayy  阅读(50)  评论(0)    收藏  举报
/* 鼠标点击求赞文字特效 */ /*鼠标跟随效果*/