• 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
    ", 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

        

     

          

          

  • 相关阅读:
    C2B未来:大数据定制
    Linux开发环境的搭建和使用——Linux 常用的命令使用
    iebook 发布到网站 独家秘诀
    ZOJ 2679 Old Bill(数学)
    DFA最小化 -- Hopcroft算法 Python实现
    How to initialize a static const map in c++?
    新秀操作和维护注意事项:Windows关于使用Xshell管理你的云主机
    HDOJ 4883 TIANKENG’s restaurant
    谈论multistage text input(中国输入法)下一个UITextView内容长度的限制
    iOS 网络错误-分类
  • 原文地址:https://www.cnblogs.com/juruohx/p/9622856.html
Copyright © 2020-2023  润新知