NOIP倒数第60天 - 点双。边双。割点和桥。强联通分量

一、割点

  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\n", 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 }
View Code

 

    

 

 

      

      

posted @ 2018-09-10 20:54  Iamhx  阅读(297)  评论(0编辑  收藏  举报