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

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

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

点双连通的一些特点

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

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

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

 

首先这个人的博客挺好的: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 ? '\n' : ' ');
            }
        }
        int ans = n;
        for (int i = 1; i <= n; i++){
            if (odd[i]) ans--;
        }
        printf("%d\n", 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\n", 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\n", bcccnt);
        printf("Case %d: %lld %lld\n", ++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\n", (ans + 1) / 2);
94     }
95     return 0;
96 }
View Code

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

 

四:

 

五:

 

六:

 

七:

 

posted @ 2016-08-28 11:07  知る奇迹に  阅读(378)  评论(0编辑  收藏  举报