本题求的是点双连通分量。还是先讲一下有关的概念:

  1. 割点:在一个无向连通图中,如果删除某个点和这个点关联的所有边,使剩下的图不再连通的点。

  2. 点双连通图:在一个无向连通图中,对于任意一个点,若删除这个点和这个点关联的所有边后,剩下的图仍能连通的图。

  3. 点双连通分量: 在无向图 $G$ 中,如果一个点双连通子图 $G’$ 不是任何一个点双连通子图的真子集,则图 $G’$ 为图 $G$ 的极大点双连通子图,即点双连通分量

接下去讲解法(以下用 dfn 数组记录访问每个点的时间,用一个 low(u) 来表示 $u$ 以及其后代最多经过一条反向边能回到的最早的时间戳)。

在一个连通图中,若点 $u$ 为树根,对于点 $u$ ,若当且仅当它有两个或者更多的子结点时,则它为割点;若点 $u$ 为非根的结点,则有定理—在无向连通图 $G$ 的 DFS 树中,$u$ 是个割点当且仅当 $u$ 存在一个子结点 $v$,使得 $v$ 及其所有后代都没有反向边连回 $u$ 的祖先(注意:不包括 $u$)。

则当一条边 $(u,v)$ 有 low[v] >= dfn[u] 时 $u$ 为割点。将 low[u] 更新时,分为树边 low[u] = min (low[u],dfn[u]) 和反向边 low[u] = min (low[u],dfn[v]) 即可。

记录点双连通分量也很简单,首先用栈压入保存元素,等找到割点后再弹出并用 set 记录就行。为什么要用 set?原因很简单,就是它能不重复且有序的记录元素。

最后需要注意一下输出的大小顺序,直接对 set 做一次 sort 即可输出。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <set>
#include <stack>
#define init(x) memset (x,0,sizeof (x))
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
using namespace std;
const int MAX = 3e5 + 5;
const int MOD = 1e9 + 7;
int n,m,cnt,bcc_cnt,times;
int dfn[MAX],low[MAX],bcc_in[MAX];
int to[MAX << 1],head[MAX << 1],nxt[MAX << 1];
bool vis[MAX];
set <int> bcc[MAX];
stack <pair <int,int> > s;//用 pair 记录一条边的 u 与 v
inline int read ();
void _add (int u,int v);
void dfs (int u,int fa);
int main ()
{
//freopen (".in","r",stdin);
//freopen (".out","w",stdout);
n = read ();m = read ();
for (int i = 1;i <= m;++i)
{
int u = read (),v = read ();
_add (u,v);_add (v,u);//双向边,需要注意数组的大小
}
for (int i = 1;i <= n;++i)
if (!dfn[i]) dfs (i,-1);//图不一定联通,需全部遍历
printf ("%d\n",bcc_cnt);
sort (bcc + 1,bcc + 1 + bcc_cnt);
for (int i = 1;i <= bcc_cnt;++i)
{
for (set <int>::iterator it = bcc[i].begin ();it != bcc[i].end ();++it) printf ("%d ",*(it));//set 的输出需要注意
puts ("");
}
return 0;
}
inline int read ()
{
int s = 0;int f = 1;
char ch = getchar ();
while ((ch < '0' || ch > '9') && ch != EOF)
{
if (ch == '-') f = -1;
ch = getchar ();
}
while (ch >= '0' && ch <= '9')
{
s = s * 10 + ch - '0';
ch = getchar ();
}
return s * f;
}
void _add (int u,int v)
{
to[++cnt] = v;
nxt[cnt] = head[u];
head[u] = cnt;
}
void dfs (int u,int fa)
{
dfn[u] = low[u] = ++times;
int child = 0;
for (int i = head[u];i;i = nxt[i])
{
int v = to[i];
if (!dfn[v])
{
s.push ({u,v});
++child;
dfs (v,u);
low[u] = min (low[u],low[v]);
if (low[v] >= dfn[u])//一个割点出现,记录一个点双连通分量
{
++bcc_cnt;
while (1)
{
int x = s.top ().first,y = s.top ().second;
s.pop ();
bcc_in[x] = bcc_in[y] = bcc_cnt;
bcc[bcc_cnt].insert (x);
bcc[bcc_cnt].insert (y);
if (x == u && y == v) break;
}
}
}
else if (dfn[v] < dfn[u] && v != fa)//特殊情况
{
s.push({u,v});
low[u] = min (low[u],dfn[v]);
}
}
}