洛谷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';
}
}

浙公网安备 33010602011771号