首页 > 代码库 > POJ 3352 & 3177 无向图的边-双连通分量(无重边 & 重边)

POJ 3352 & 3177 无向图的边-双连通分量(无重边 & 重边)

无向图的边-双连通分量

无向图的双连通分量实际上包含两个内容:点-双连通分量、边-双连通分量
点-双连通分量是指:在该连通分量里面,任意两个点之间有多条点不重复的路径(不包括起点、终点)
边-双连通分量是指:在该连通分量里面,任意两个点之间有多条边不重复的路径

在求解点-双连通分量时,无向图有没有重边都没有关系,因为一个点只能经过一次(有重边也无妨)
该篇文章并不深入讨论点-双连通分量,给出代码给有兴趣的参考参考:(也可以看看POJ2942这道题, 解题报告)

/*=========================================
    无向图求点-双连通分量 (任意两个点之间至少存在两条“点不重复”的路径)
    复杂度:O(E + V)
    在做割点的过程中,将每条边push进栈,当碰到割点的时候,将所有的边pop出来,直到遇到(u,v)为止
    每个连通分量存在bcc[cnt]中
    //有没有重边无所谓(因为一个点只能经过一次)
=========================================*/
const int maxn = 1100;
int bccno[maxn], dfn[maxn], low[maxn], cnt, n; //其中割点的bccno[]无意义
bool cut[maxn];
int dfs_clock;
vector<int> g[maxn], bcc[maxn];
struct edge {
    int u, v;
    edge(int _u, int _v) {
        u = _u, v = _v;
    }
};
stack<edge> s;
void dfs(int u, int f) {
    low[u] = dfn[u] = ++dfs_clock;
    int child = 0;
    for (int i = 0; i < g[u].size(); i++) if (g[u][i] != f) {
            int v = g[u][i];
            edge e(u, v);
            if (!dfn[v]) {
                s.push(e);
                dfs(v, u);
                child++;
                if (low[v] < low[u]) low[u] = low[v];
                if (low[v] >= dfn[u]) {
                    cut[u] = true;
                    cnt++;
                    bcc[cnt].clear();  //cnt从1开始!
                    while(1) {
                        edge x = s.top();
                        s.pop();
                        if (bccno[x.u] != cnt) bcc[cnt].push_back(x.u), bccno[x.u] = cnt; //这里存的是每个点-双连通分量里的点(如果要存边需要修改)
                        if (bccno[x.v] != cnt) bcc[cnt].push_back(x.v), bccno[x.v] = cnt;
                        if (x.u == u && x.v == v) break;
                    }
                }
            }
            else if (dfn[v] < low[u]) {
                s.push(e);
                low[u] = dfn[v];
            }
        }
    if (f == -1 && child < 2) cut[u] = false;
}
void find_bcc(int n) {
    memset(dfn, 0, sizeof(dfn));
    memset(cut, 0, sizeof(cut));
    memset(bccno, 0, sizeof(bccno));
    while(!s.empty()) s.pop();
    dfs_clock = cnt = 0;
    for (int i = 1; i <= n; i++) if (!dfn[i]) dfs(i, -1);
}


在求解边-双连通分量的时候,网上很多代码都是错误的! 
正确的做法是对原图进行一次dfs,求出割边并标记。然后再dfs一次求出每个连通分量(因为去除了桥,将每个连通分量分开了)
而对于边-双连通分量,一个图有没有重边将对结果有影响。
举个例子:
无重边的图: 1 --- 2  (有两个边-连通分量)
有重边的图: 1---- 2 (其中1到2之间有重边)那么只有一个边-连通分量

1. 首先先不考虑重边,可能大家有找到网上的一些代码,通过对原图进行一次dfs,然后可以得到所有的边-双连通分量的low[]值相同。其实这种做法是错误的,因为当一个连通分量里面出现2个及以上的环时,该代码就会出现问题。
比如该组数据:
16 21
1 8
1 7
1 6
1 2
1 9
9 16
9 15
9 14
9 10
10 11
11 13
11 12
12 13
11 14
15 16
2 3
3 5
3 4
4 5
3 6
7 8


用上述的方法得到的答案为0,实际上应该是1。

所以求解边-双连通分量时,要先求割边,然后再dfs。

2. 如果无向图有重边,那么就不能用vector来存边,用邻接表来存边,方式和网络流建边相似。建立一条正向边 i,再建立一条反向边 i ^ 1。这样就能保证重边能被处理到。
具体代码如下: 可以参考着做一下POJ 3352 (无重边)和3177(有重边) (可惜两道题的数据都不强)

const int maxn = 5100;
const int maxm = 10100;
int g[maxn], n, m;
int bccno[maxn], dfn[maxn], low[maxn], bcc_cnt, dfs_clock, cnt;
bool vis[maxm * 2], isbridge[maxm * 2];
struct node {
    int v, nxt;
} e[maxm * 2];
void add(int u, int v) {
    e[++cnt].v = v;
    e[cnt].nxt = g[u];
    g[u] = cnt;

    e[++cnt].v = u;
    e[cnt].nxt = g[v];
    g[v] = cnt;
}
void init() {
    cnt = 1;
    memset(g, 0, sizeof(int) * (n + 10));
    int u, v;
    for (int i = 0; i < m; i++) {
        scanf("%d%d", &u, &v);
        add(u, v);
    }
}
void dfs(int u) {
    dfn[u] = low[u] = ++dfs_clock;
    for (int i = g[u]; i; i = e[i].nxt) {
        int v = e[i].v;
        if (!dfn[v]) {
            vis[i] = vis[i ^ 1] = true;
            dfs(v);
            low[u] = min(low[v], low[u]);
            if (low[v] > dfn[u]) isbridge[i] = isbridge[i ^ 1] = true;
        } else if (dfn[v] < dfn[u] && !vis[i]) {
            vis[i] = vis[i ^ 1] = true;
            low[u] = min(low[u], dfn[v]);
        }
    }
}
void dfs_bcc(int u, int id) {
    bccno[u] = id;
    for (int i = g[u]; i; i = e[i].nxt) if (!isbridge[i]) {
            int v = e[i].v;
            if (!bccno[v]) dfs_bcc(v, id);
        }
}
void find_bcc(int n) {
    dfs_clock = bcc_cnt = 0;
    memset(dfn, 0, sizeof(dfn));
    memset(bccno, 0, sizeof(bccno));
    memset(vis, 0, sizeof(vis));
    memset(isbridge, 0, sizeof(isbridge));
    for (int i = 1; i <= n; i++) if (!dfn[i]) dfs(i);
    for (int i = 1; i <= n; i++) if (!bccno[i]) dfs_bcc(i, ++bcc_cnt);
}

POJ 3352 & 3177 无向图的边-双连通分量(无重边 & 重边)