• 双连通分量的题目列表(一)


    双连通分量:定义:给一个无向图,其中的极大子图中的每个点两两可达,那么就说明这个是一个双连通分量。

    点双连通:如果任意两点至少存在两条点不重复的路径,则说明这个图是点双连通。

    点双连通的一些特点

    ①每条边恰好属于一个双连通分量,但不同的双连通分量可能有公共点。

    ②不同的双连通分量最多只能有一个公共点

    ③任意割顶都是至少两个不同的双连通分量之间路径的公共点(去除顶点的特殊情况)

    首先这个人的博客挺好的:https://www.byvoid.com/blog/biconnect/

    然后下面是我自己总结的一个东西

    边双连通:如果任意的两点至少存在两条便不重复的路径,那么就说明是边双连通。

    ①除了桥不属于任何边双连通分量之外,其他每条边恰好属于一个变双连通分量

    ②把桥删除之后,每个连通分量对应原图的一个边双连通分量

    双连通分量取出来的是环(除了只有两个顶点的,而且这两个顶点不算双连通,例如0-0)

    题目列表

    ①点双连通分量+二分图染色判奇偶环 LA 3523 圆桌骑士(一)

    ②利用点双连通最小方案数问题 LA 5135 井下矿工(二)

    ③边双连通分量+缩点:加入最少边数能让图变成双连通图 POJ 3352(三) 经典题目      和LA 4287 强连通最后的ans的判断方法做一下区别(强连通一)

    一:圆桌骑士 UVALIVE3523 蓝书316

    题目大意:n个歧视,有m个憎恶关系,现在让n个骑士开会,相互憎恶的骑士不能出现在同一个会上,且开会的人数为奇数。问有多少个骑士在任何一个会上都不能出现?

    思路:首先我们反转关系,将没有憎恶关系的骑士之间连边。然后我们得到另外一个题目,即有多少个骑士能出现在奇环中。然后再分析一下二分图,二分图形成环一定是偶数环,所以我们现在的目的是取出所有的环。然后进行二分图染色,如果不能成功染色的,说明是可以成功匹配的,就用保存下来就好了。

    //看看会不会爆int! 或者绝对值问题。
    #include <bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define all(a) a.begin(), a.end()
    const int maxn = 1000 + 5;
    const int maxm = 1000000 + 5;
    int n, m;
    vector<int> G[maxn], bcc[maxn];
    int bccno[maxn];
    int a[maxn][maxn];
    int dfstime, bcnt;
    int lowu[maxn], pre[maxn];
    bool iscut[maxn];
    stack<pair<int, int> > s;
    
    int find_bcc(int u, int fa){
        int child = 0;
        int lowu = pre[u] = ++dfstime;
        int len = G[u].size();
        for (int i = 0; i < len; i++){
            int v = G[u][i];
            pair<int, int> p = mk(u, v);
            if (pre[v] == -1){
                s.push(p);
                child++;
                int lowv = find_bcc(v, u);
                lowu = min(lowv, lowu);
                if (lowv >= pre[u]){
                    iscut[u] = true;
                    bcnt++;
                    while (true){
                        pair<int, int> g = s.top(); s.pop();
                        if (bccno[g.fi] != bcnt) bcc[bcnt].pb(g.fi), bccno[g.fi] = bcnt;
                        if (bccno[g.se] != bcnt) bcc[bcnt].pb(g.se), bccno[g.se] = bcnt;
                        if (g.fi == u && g.se == v) break;
                    }
                }
            }
            else if (pre[v] < pre[u] && v != fa){
                s.push(p);
                lowu = min(lowu, pre[v]);
            }
        }
        if (fa < 0 && child == 1) iscut[u] = false;
        return lowu;
    }
    
    void init(){
        dfstime = bcnt = 0;
        memset(a, 0, sizeof(a));
        memset(iscut, false, sizeof(iscut));
        memset(pre, -1, sizeof(pre));
        for (int i = 1; i <= n; i++){
            G[i].clear(); bcc[i].clear();
        }
        for (int i = 1; i <= m; i++){
            int u, v;
            scanf("%d%d", &u, &v);
            a[u][v] = a[v][u] = 1;
        }
        for (int i = 1; i <= n; i++){
            for (int j = i + 1; j <= n; j++){
                if (a[i][j] == 0){
                    G[i].pb(j); G[j].pb(i);
                }
            }
        }
    }
    
    int color[maxn];
    bool draw(int u, int ty){
        int len = G[u].size();
        for (int i = 0; i < len; i++){
            int v = G[u][i];
            if (bccno[v] != ty) continue;
            if (color[v] == color[u]) return false;
            if (color[v] == -1){
                color[v] = 1 - color[u];
                if (!draw(v, ty)) return false;
            }
        }
        return true;
    }
    int odd[maxn];
    int main(){
        while (~scanf("%d%d", &n, &m) && (n + m) > 0){
            init();
            memset(odd, 0, sizeof(odd));
            for (int i = 1; i <= n; i++){
                if (pre[i] == -1){
                    find_bcc(i, -1);
                }
            }
            //目前找奇数环
            for (int i = 1; i <= bcnt; i++){
                int len = bcc[i].size();
                memset(color, -1, sizeof(color));
                for (int j = 0; j < len; j++){
                    int v = bcc[i][j];
                    bccno[v] = i;
                }
                color[bcc[i][0]] = 0;
                if (!draw(bcc[i][0], i)){
                    for (int j = 0; j < len; j++){
                        int v = bcc[i][j];
                        odd[v] = true;
                    }
                }
                for (int j = 0; j < len; j++){
                    int v = bcc[i][j];
                    //printf("%d%c", color[v], j == len - 1 ? '
    ' : ' ');
                }
            }
            int ans = n;
            for (int i = 1; i <= n; i++){
                if (odd[i]) ans--;
            }
            printf("%d
    ", ans);
        }
        return 0;
    }
    View Code

    关键:关系图的反转、二分图的使用

    二:井下矿工 LA 5135 蓝书318

    题目大意:有n条路矿井,每条路连接两个顶点,没有重边。你的任务是在节点处安装太平井,不得不管哪个连接点倒塌,不在此连接点的所有旷工都能到达太平井(只有倒塌的那条路不能走)。问,选择安装太平井的最小数目,和该数目下的最小方案数。

    思路一:点双连通分量

    假设安装太平井的地方涂黑。我们发现,涂黑点的最优点一定不会是割点,因为如果涂黑这里,那么如果这里坏了,上下两条路就不相通,所以还要再额外在上下两条路在添加一个黑点。因此我们发现,在双连通分量里面只要涂黑不是割点的地方就行了。而且我们还发现,一个双连通分量里面如果有两个割点,那么这个双连通分量就不需要添加黑点了。

    //看看会不会爆int! 或者绝对值问题。
    #include <bits/stdc++.h>
    using namespace std;
    #define LL long long
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define all(a) a.begin(), a.end()
    const int maxn = 100000 + 5;
    vector<int> G[maxn], bcc[maxn];
    int n, m, dfstime, bcccnt;
    bool iscut[maxn];
    int bccnu[maxn], pre[maxn];
    stack<pair<int, int> > s;
    
    int dfs(int u, int fa){
        int lowu = pre[u] = ++dfstime;
        int child = 0;
        int len = G[u].size();
        for (int i = 0; i < len; i++){
            int v = G[u][i];
            pair<int, int> p = mk(u, v);
            if (pre[v] == -1){
                child++;
                s.push(p);
                int lowv = dfs(v, u);
                lowu = min(lowu, lowv);
                if (lowv >= pre[u]){
                    iscut[u] = true;
                    bcccnt++;
                    while (true){
                        pair<int, int> pr = s.top(); s.pop();
                        if (bcccnt != bccnu[pr.fi]) bccnu[pr.fi] = bcccnt, bcc[bcccnt].pb(pr.fi);
                        if (bcccnt != bccnu[pr.se]) bccnu[pr.se] = bcccnt, bcc[bcccnt].pb(pr.se);
                        if (pr.fi == u && pr .se == v) break;
                    }
                }
            }
            else if (pre[v] < pre[u] && v != fa){
                s.push(p); lowu = min(lowu, pre[v]);
            }
        }
        if (fa < 0 && child == 1) iscut[u] = false;
        return lowu;
    }
    
    void find_bcc(){
        dfstime = bcccnt = 0;
        memset(iscut, false, sizeof(iscut));
        memset(bccnu, 0, sizeof(bccnu));
        memset(pre, -1, sizeof(pre));
        dfs(1, -1);
    }
    map<int, int> mp;
    int main(){
        int kase = 0;
        while (scanf("%d", &m) == 1 && m){
            mp.clear();
            for (int i = 1; i <= m * 2; i++) {
                G[i].clear();
                bcc[i].clear();
            }
            n = 0;
            for (int i = 1; i <= m; i++){
                int u, v; scanf("%d%d", &u, &v);
                if (mp[u] == 0) mp[u] = ++n;
                if (mp[v] == 0) mp[v] = ++n;
                u = mp[u], v = mp[v];
                //printf("u = %d v = %d
    ", u, v);
                G[u].pb(v); G[v].pb(u);
            }
            find_bcc();
            LL num = 0, ans = 1;
            if (bcccnt == 1){
                num = 2;
                ans = 1LL * bcc[1].size() * (bcc[1].size() - 1) / 2;
            }
            else {
                for (int i = 1; i <= bcccnt; i++){
                    int len = bcc[i].size();
                    int cnt = 0;
                    for (int j = 0; j < len; j++){
                        if (iscut[bcc[i][j]]) cnt++;
                    }
                    if (cnt == 1){
                        num++;
                        ans = ans * 1LL * (len - cnt);
                    }
                }
            }
            //printf("bcccnt = %d
    ", bcccnt);
            printf("Case %d: %lld %lld
    ", ++kase, num, ans);
        }
        return 0;
    }
    View Code

    关键:点双对割点的运用

    思路二:割点

    通过dfs求出割点的位置,然后再对不是割点的进行dfs,如果经过两个不同的割点,那么就不乘,反之乘以dfs下来的数目。

    三:边双连通题+缩点 POJ 3352

    题目大意:给一张图,最少加入多少个图片能使得原来的图变成双连通图?

    思路:dfs求出所有的桥,然后利用边双连通分量来得到每个极大子图。每个极大子图都对应着一个bcc_cnt,然后极大子图里面是保证了是双连通的。那么题目就换成了,两个bcc_cnt不同的子图之间有几条边,如果边数是1,就说明要加边。(这里利用的思想就是把bcc_cnt的这个极大子图看成一个点来对待,看看每个点之间有几条边)

     1 //看看会不会爆int! 或者绝对值问题。
     2 #include <cstdio>
     3 #include <cstring>
     4 #include <iostream>
     5 #include <algorithm>
     6 #include<stack>
     7 #include<vector>
     8 using namespace std;
     9 #define LL long long
    10 #define pb push_back
    11 #define mk make_pair
    12 #define fi first
    13 #define se second
    14 #define all(a) a.begin(), a.end()
    15 const int maxn = 1000 + 5;
    16 stack<int> s;
    17 vector<int> G[maxn], bcc[maxn];
    18 int bccno[maxn], pre[maxn], iscut[maxn];
    19 int n, m, bcc_cnt, dfstime;
    20 
    21 int dfs(int u, int fa){
    22     int lowu = pre[u] = ++dfstime;
    23     int len = G[u].size();
    24     int child = 0;
    25     s.push(u);
    26     for (int i = 0; i < len; i++){
    27         int v = G[u][i];
    28         if (pre[v] == -1){
    29             child++;
    30             int lowv = dfs(v, u);
    31             lowu = min(lowv, lowu);
    32             if (lowv > pre[u]){
    33                 iscut[u] = true;
    34             }
    35         }
    36         else if (pre[v] < pre[u] && v != fa){
    37             lowu = min(lowu, pre[v]);
    38         }
    39     }
    40     if (lowu == pre[u]){
    41         bcc_cnt++;
    42         while (true){///边双连通分量是不存在重点的
    43             int v = s.top(); s.pop();
    44             bcc[bcc_cnt].pb(v);
    45             if (v == u) break;
    46         }
    47     }
    48     if (fa == -1 && child == 1) iscut[u] = false;
    49     return lowu;
    50 }
    51 int cnt[maxn], color[maxn];
    52 int main(){
    53     while (scanf("%d%d", &n, &m) == 2){
    54         for (int i = 1; i <= n; i++){
    55             G[i].clear(); bcc[i].clear();
    56         }
    57         for (int i = 1; i <= m; i++){
    58             int u, v; scanf("%d%d", &u, &v);
    59             G[u].pb(v), G[v].pb(u);
    60         }
    61         memset(bccno, 0, sizeof(bccno));
    62         memset(iscut, false, sizeof(iscut));
    63         memset(pre, -1, sizeof(pre));
    64         dfstime = bcc_cnt = 0;
    65         for (int i = 1; i <= n; i++){
    66             if (pre[i] == -1){
    67                 dfs(i, -1);
    68             }
    69         }
    70         memset(cnt, 0, sizeof(cnt));
    71         memset(color, 0, sizeof(color));
    72         int ans = 0;///我们需要知道在连通分量里面有几条边
    73         
    74         for (int i = 1; i <= bcc_cnt; i++){
    75             int len = bcc[i].size();
    76             for (int j = 0; j < len; j++){
    77                 int v = bcc[i][j];
    78                 color[v] = i;
    79             }
    80         }
    81         for (int i = 1; i <= n; i++){
    82             int len = G[i].size();
    83             for (int j = 0; j < len; j++){
    84                 int v = G[i][j];
    85                 if (color[i] != color[v]) {
    86                     cnt[color[i]]++;
    87                 }
    88             }
    89         }
    90         for (int i = 1; i <= bcc_cnt; i++){
    91             if (cnt[i] == 1) ans++;
    92         }
    93         printf("%d
    ", (ans + 1) / 2);
    94     }
    95     return 0;
    96 }
    View Code

    学习:边双连通的做法,缩点的技巧,任意两个边双连通是不存在公共点的

    四:

    五:

    六:

    七:

  • 相关阅读:
    oracle的常见问题与解决
    final、finally、finalize的区别
    java中读取程序运行时间
    数据库设计与SQL优化的建议
    Eclipse 快捷键操作和常用设置
    OO设计原则
    structs常用的Action
    java的深复制与浅复制
    python进制(十进制,八进制,十六进制)
    linux的shell基础
  • 原文地址:https://www.cnblogs.com/heimao5027/p/5813863.html
Copyright © 2020-2023  润新知