一、割点
1 . 割点的定义。
在无向连通图中,如果将其中一个点以及所有连接该点的所有边去掉,图就不再连通,那么这个点就叫做割点。
2 . tarjan算法的引入
Tarjan算法是利用时间戳来解决连通分量问题的一类优秀算法。Tarjan算通过dfs,构成一颗dfs树。在Tarjan算法中,给每个点x定义了low值和dfn值, 分别表示该点x第一次被dfs到的时间戳和点x通过访问边可以到达的最早的时间。下文中,我们用 dfn[x] 代表点x的dfn值,low[x] 代表点x的low值。
3 . 割点的求法
首先选定一个根节点,从该根节点开始遍历整个图(使用DFS),记录下每个点的low值和dfn值。 然后通过low值和dfn值来考虑一个点是否可以成为割点。
假如我们访问到了点x, 那么我们按照现在的时间戳给点x的dfn值和low值赋值,即 dfn[x] = low[x] = ++ dfs_clock。
根据我们的定义,我们可以知道,dfn[x] 不会再改变,但是 low[x] 却不一定有那么大,x还有很多边没有访问,还有机会访问到dfs序更加小的点。
我们通过顺序访问与点x相连的边,然后通过手段,可以得到真正的low【x】。然后判断割点。
考虑点x 是否为割点:
如果该点恰好是我们dfs树的根节点,那么如果它的子树不只一个,那么该点一定为割点。
如果该点不是我们dfs树的根节点,那么我们比较它的low【x】和dfn【x】,显然如果 low【x】< dfn【x】,则x的子树中一定有边越过了点x,和一些较x更早访问过的点连在了一点,那么点x一定不是割点,否则如果low【x】 == dfn【x】,则点x一定是割点。
二、 桥
1 . 桥的定义
桥是存在于无向图中的一条边,如果去掉这一条边,那么整张无向图会分为两个互不连通的子图,则这条边称为桥。
2 . 桥的求法
首先对于每个联通块跑一遍Tarjan算法,得到每个点的low值和dfn值。再对于每一条边考虑这条边是否为桥。
枚举边x,假设这条边连接的两个点分别为x 和 y , 其中dfn【x】< dfn【y】, 即 x 比 y 先被访问。
如果low【y】<= dfn【x】,则代表y可以上翻到x上面,那么我们删去边x后,以v为根的子树仍可以访问到上面,不会被分成两部分。
如果low【y】> dfn【x】,则该边为割边。
3 . 注意事项
由于实现方法的不同,我们需要注意重边的情况,如果发生重边,显然重复的边删去一条不会对图的连通性产生任何影响。
为了避免重边带来的错误,我们需要在dfs中下传边的编号,即连接 x - v的边的编号,而不是 点v的父亲x。
三、双连通分量
1 . 定义
对于一个连通图,如果任意两点至少存在两条点不重复路径,则称这个图为点双连通的(简称点双连通)。
对于一个连通图,如果任意两点至少存在两条边不重复路径,则称这个图为边双连通的(简称边双连通)。
点双连通图的定义等价于任意两条边都同在一个简单环中,而边双连通图的定义等价于任意一条边至少在一个简单环中。
对一个无向图,点双连通的极大子图称为点双连通分量(简称双连通分量),边双连通的极大子图称为边双连通分量。
2 . 点双连通分量的性质
(1) 对于点双连通分量,删除任意一点连通性不变。其中不会含桥(否则桥两边分别选一个点都不会满足点双定义),且环与环必定有公共边(否则两个环分别选一个点都不不会满足点双的定义),且公共点至少有两个,简单圈里面的点一定属于同个强联通分量(因为彼此之间一定都有路相连,且至少有两条)。
(2) 任意一个割点都至少是两个点双连通的公共点,点双连通分量一定是一个双连通分量。
3 . 点双连通分量的求法
回顾割点的求法,我们发现,当我们找到割点的时候,就已经完成了一次对某个极大点双连通子图的访问了。因此我们可以想到将dfs过程中遍历过的点保存起来。为了实现算法,我们可以在求解割点的过程中用一个栈保存遍历过的边(不可以是点,因为不同的点双连通分量之间存在公共点即割点),之后每当找到一个点双联通分量,即子节点v与父节点u满足low【v】>= dfn【u】,我们就把栈里面的边逐个pop,直到遇到当前边。
4 . 点双模板代码(待补)
5 . 边双连通求法
让我们先用Tarjan 算法找到所有的桥,标记好桥后,再dfs一遍, 保证不走被标记的点,找到的连通块就是边双连通分量。
6 . 边双连通分量代码
例题戳这儿~
1 #include<bits/stdc++.h> 2 3 using namespace std; 4 5 const int maxn = 50000 + 5; 6 7 int n, m, link[maxn << 1], toit[maxn << 1], nxt[maxn << 1], cnt = 1; 8 bool used[maxn << 1]; 9 10 void Add(int x, int y) { 11 ++ cnt; toit[cnt] = y; nxt[cnt] = link[x]; link[x] = cnt; 12 ++ cnt; toit[cnt] = x; nxt[cnt] = link[y]; link[y] = cnt; 13 } 14 15 int dfs_clock = 0, dfn[maxn], low[maxn]; 16 17 void dfs(int x, int fat, int w) { 18 dfn[x] = low[x] = ++ dfs_clock; 19 for (int i = link[x]; i; i = nxt[i]) { 20 int v = toit[i]; if (v == fat) continue; 21 if (dfn[v]) low[x] = min(low[x], dfn[v]); 22 else { 23 dfs(v, x, i); 24 low[x] = min(low[x], low[v]); 25 } 26 } 27 if (low[x] == dfn[x]) { 28 used[w ^ 1] = used[w] = true; 29 } 30 } 31 32 int vis[maxn]; 33 34 void Dfs(int x, int fat, int num) { 35 vis[x] = num; 36 for (int i = link[x]; i; i = nxt[i]) if (! used[i]){ 37 int v = toit[i]; if (v == fat || vis[v]) continue; 38 Dfs(v, x, num); 39 } 40 } 41 42 int main(void) { 43 scanf("%d%d", &n, &m); 44 for (int i = 1; i <= m; ++ i) { 45 int x, y; scanf("%d%d", &x, &y); 46 Add(x, y); 47 } 48 memset(low, 0, sizeof(low)); 49 memset(dfn, 0, sizeof(dfn)); 50 for (int i = 1; i <= n; ++ i) if (! dfn[i]) dfs(i, 0, 0); 51 // for (int i = 2; i <= m * 3; ++ i) if (used[i]) printf("%d ", i); 52 for (int i = 1, tot = 0; i <= n; ++ i) if (vis[i] == 0) Dfs(i, 0, ++ tot); 53 int Q; scanf("%d", &Q); 54 while (Q --) { 55 int s, t; scanf("%d%d", &s, &t); 56 puts(vis[s] == vis[t] ? "Yes" : "No"); 57 } 58 return 0; 59 }