洛谷P8436 题解

传送门:P8436 【模板】边双连通分量

何为边双连通分量?

首先简单介绍一下割边:割边,又叫做桥,指在一张无向图中如果有一条边被删去后使得这张图不连通,那么我们就称这一条边为这张无向图的割边,需要注意的是,割边可能不止一条

然后就能了解边双连通分量了:边双连通分量简称边双。如果一张无向图不存在割边,那么我们称这张图为边双连通图。而一张图的极大边双连通子图就被称为边双连通分量。

求法

对于边双,我们可以使用 Tarjan 算法进行求解。

引入两个数组:

  • \(low_i\):追溯值数组,表示在搜索树中 \(i\) 的儿子节点和它通过非树边能达到的节点的最小编号。

  • \(dfn_i\):时间戳数组,表示 \(i\) 点被遍历到的次序,即该节点的编号。

我们还需要一个栈来存储目前遍历到且还未划分进边双的节点。

我们看图来理解 Tarjan 的大致流程,这里给出一张无向图。

选择任意一个节点开始遍历,每到一个新节点都将它压入栈中并初始化 \(dfn_i\)\(low_i\)

当从 \(1\) 开始遍历到 \(4\) 节点时各个节点的信息如下。

此时继续遍历至 \(2\) 节点。

此时栈中存储了 \(1,5,4,3,2\) 几个节点。

\(2\) 节点时发现遍历到了已经遍历过且正在栈中的 \(3\) 节点,用 \(dfn_3\) 来更新 \(low_2\)\(low_2\) 的值被更新为 \(4\)

接着回溯到 \(3\) 节点时发现 \(dfn_3 = low_3\),此时开始对这个边双进行处理,不断地弹栈,直到将节点本身从栈中弹出为止,期间弹出的所有节点都属于同一个边双,弹栈时记录即可。

于是将 \(2,3\) 从栈中弹出,记录它们。

此时栈中存储了 \(1,5,4\) 几个节点。

继续回溯至 \(4\) 节点,发现 \(1\) 在栈中,用 \(dfn_1\) 更新 \(low_4\)\(1\)(这几段我真的没有放图)。

回溯至 \(3\) 节点,发现自己引申出的 \(4\) 节点的 \(low_4 < low_5\) 于是用 \(low_4\) 更新 \(low_5\)\(1\)

再回溯,\(low_1\) 无法更新。由于 \(low_1 = dfn_1\) 此时将栈中 \(1\)\(1\) 之前的数全弹出并记录。

此时,整张图遍历完成,边双的划分情况如下:\(bel_1\{4,5,1\},bel_2\{2,3\}\)

到此,整个题目的答案也就求出来了,不过有时因为玄学数据比较毒瘤,可能无法一次性遍历完所有点,所以需要开一层循环,内部判断当前循环到的节点是否被遍历过(检查 \(dfn_i\) 即可),如果没有遍历过(\(dfn_i = 0\)),就从这个节点开始跑 Tarjan。

Code:

#include<iostream>
#include<vector>
using namespace std;
const int N(5e5+5),M(2e6+5);
int n,m,h[N],tot=1;
bool vis[N];
struct E{
	int to,nxt;
} e[M<<1];
void add(int x,int y){
	e[++tot].nxt=h[x],e[h[x]=tot].to=y;
	e[++tot].nxt=h[y],e[h[y]=tot].to=x;
}
int dfn[N],low[N],cnt,sta[N],top,id;
vector<int> bel[N];//存储各个边双
void Tarjan(int u,int lst){
	dfn[u]=low[u]=++cnt;
	sta[++top]=u;vis[u]=1;
	for(int i=h[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(i==(lst^1)) continue;//防止走重复边,不是防重边!!!
		if(!dfn[v]){
			Tarjan(v,i);
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v]) low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u]){
		int k;id++;
		do{
			k=sta[top--];//弹栈并记录
			vis[k]=0;
			bel[id].push_back(k);
		}while(k!=u);
	}
}
int main(){
	#ifdef ytxy
	freopen("in.txt","r",stdin);
	#endif
	ios::sync_with_stdio(0);
	cin>>n>>m;
	for(int i=1,x,y;i<=m;i++){
		cin>>x>>y;
		if(x!=y) add(x,y);
	}
	for(int i=1;i<=n;i++)
		if(!dfn[i]){
			Tarjan(i,-1);//判断是否遍历过
		}
	cout<<id<<'\n';
	for(int i=1;i<=id;i++){
		cout<<bel[i].size()<<' ';
		for(int j=0;j<bel[i].size();j++) cout<<bel[i][j]<<' ';
		cout<<'\n';
	}
}
posted @ 2022-07-15 16:13  JR_ytxy  阅读(37)  评论(0)    收藏  举报