本文是《刘汝佳算法竞赛》的双连通分量一节的总结
前置名词讲解
- 割点: 若无向图G中,存在一个点,当该点被删除时,图G中的连通分量数目增加。整张图不再连通。
- 连通分量:若图G连通,则连通分量便是自身,非连通的无向图有多个连通分量。、
- 极大连通子图:对于一个连通图,极大连通子图就是其连通分量也就是 其本身,非连通图有多个连通分量则就有多个极大连通子图。主要理解的是极大,极大指的是这个连通子图不能被其他连通子图所包含。对于一个连通图来说,只有其本身才能不被其他连通子图所包含。
- 简单环:一个环,除了终点和起点是同一点外,其他点在环中只出现一次。
- 点双连通:任意两点之间都存在两条“点不重复的路径”。若图G满足,则图称作“点-双连通”的,这个要求等价于任意两条边都在同一简单环中,即内部无割点;
- 点-双连通分量:一个子图满足点双连通且在图G中是极大(在满足点-双连通的所有子图中是极大的)
性质
- 点双连通分量的任意两条边都存在同一简单环中,有一个例外。
- 图中的割点都是至少两个双连通分量的公共点,而非割点则是双连通分量的内部点。
- 由上一条性质可知,双连通分量去掉任意一点,仍然保持连通。
- 不同双连通分量之间只有一个公共点,当由多个公共点是,则必然被更大的点双连通子图所包含
- 一般点双联通图都是简单环,但下面的图仍然可以通过点双连通的测试,大概是两个点都不是割点的原因。
代码
#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<string>
#include<cstring>
#include<cstdio>
#include<stack>
//#include<map>
#pragma warning(disable:4996)
using namespace std;
int n, m;
struct Edge
{
int u, v;
Edge(int _u, int _v) :u(_u), v(_v){};
};
const int maxn = 300005;
const int mod = 998244353;
//从左至右,分别是顶点的dfs标记,割点标记,所属的bcc编号,当前的递归深度,当前的bcc编号
int pre[maxn], iscut[maxn], bccno[maxn], dfs_clock, bcc_cnt;
//图G,点双连通图
vector<int>G[maxn], bcc[maxn];
//存储遍历的边
stack<Edge>s;
int dfs(int u, int fa)
{
//lowu用于存储与该点直接相连的最远祖先(最小递归深度)
int lowu = pre[u] = ++dfs_clock;
int child = 0;
for (int i = 0; i < G[u].size(); i++)
{
int v = G[u][i];
Edge e = Edge(u, v);
if (!pre[v])
{
s.push(e);
child++;
int lowv = dfs(v, u);
lowu = min(lowu, lowv);
//若该点的所有子孙节点的最远祖先都比其小,则该点就是割点
if (lowv >= pre[u])
{
iscut[u] = true;
bcc_cnt++;
bcc[bcc_cnt].clear();
int iswire = 0;
while (1)
{
//以割点为标记,stack中保存的边就是一个点双连通分量。
Edge x = s.top(); s.pop();
iswire++;
if (bccno[x.u] != bcc_cnt)
{
bcc[bcc_cnt].push_back(x.u);
bccno[x.u] = bcc_cnt;
}
if (bccno[x.v] != bcc_cnt)
{
bcc[bcc_cnt].push_back(x.v);
bccno[x.v] = bcc_cnt;
}
if (x.u == u&&x.v == v)break;
}
if (iswire == bcc[bcc_cnt].size())
//当点双连通图中的点和边相等时,便是简单环
}
}
//当其邻接点是深度较低的点,则比较lowu
else if (pre[v] < pre[u] && v != fa)
{
s.push(e);
lowu = min(lowu, pre[v]);
}
}
//只有当父节点拥有多个子节点时,才可以是割点。
if (fa < 0 && child == 1)
iscut[u] = 0;
return lowu;
}
void find_bcc(int n)
{
wire = 0;
memset(pre, 0, sizeof(pre));
memset(iscut, 0, sizeof(iscut));
memset(bccno, 0, sizeof(bccno));
dfs_clock = bcc_cnt = 0;
for (int i = 1; i <= n; i++)
{
if (!pre[i])dfs(i, -1);
}
}