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);
}

时间: 2024-08-13 11:45:49

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

POJ 3352 Road Construction(边—双连通分量)

http://poj.org/problem?id=3352 题意: 给出一个图,求最少要加多少条边,能把该图变成边—双连通. 思路:双连通分量是没有桥的,dfs一遍,计算出每个结点的low值,如果相等,说明属于同一个双连通分量. 接下来把连通分量缩点,然后把这些点连边. 对于一棵无向树,我们要使得其变成边双连通图,需要添加的边数 == (树中度数为1的点的个数+1)/2. 1 #include<iostream> 2 #include<algorithm> 3 #include&

POJ 3177 Redundant Paths (桥,边双连通分量,有重边)

题意:给一个无向图,问需要补多少条边才可以让整个图变成[边双连通图],即任意两个点对之间的一条路径全垮掉,这两个点对仍可以通过其他路径而互通. 思路:POJ 3352的升级版,听说这个图会给重边.先看3352的题解http://www.cnblogs.com/xcw0754/p/4619594.html. 其实与3352不同的就是重边出现了怎么办?假如出现的重边刚好是桥呢? 首先要知道,[割点]可以将两个[点双连通分量]隔开来,因为仅一个[点双连通分量]中肯定无割点,那么每两个点对都同时处于若干

uva 10792 无向图的边双连通分量

题意:给定一个无向图,要求把所有无向边改成有向边,并且添加最少的有向边,是的新的无向图连通. 首先,这题是先要明白,有向图的强连通分量,如果把所有的边都变成无向的,就是无向图的边双连通分量. 恩,本来以为边双连通分量又是求桥又是绕过桥dfs很麻烦想想就不想做..后来无意中在别人的题解上看到一个结论(好厉害..) 方法如下: 对无向图执行dfs求割点,然后对于任意点i和j,如果low[i]==low[j],那么它们属于同一个边-双连通分量 (点-双连通分量内的两个点的low[]值不一定相同,自己画

ZOJ2588.Burning Bridges——边双连通分量,有重边

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1588 题目描述: Ferry 王国是一个漂亮的岛国,一共有N 个岛国.M 座桥,通过这些桥可以从每个小岛都能到达任何一个小岛.很不幸的是,最近Ferry 王国被Jordan 征服了.Jordan 决定烧毁所有的桥.这是个残酷的决定,但是Jordan 的谋士建议他不要这样做,因为如果烧毁所有的桥梁,他自己的军队也不能从一个岛到达另一个岛.因此Jordan 决定烧尽可能多的桥,只

POJ 3177 Redundant Paths 边双连通分量+缩点

题目链接: poj3177 题意: 给出一张连通图,为了让任意两点都有两条通路(不能重边,可以重点),至少需要加多少条边 题解思路: 分析:在同一个边双连通分量中,任意两点都有至少两条独立路可达,所以同一个边双连通分量里的所有点可以看做同一个点. 缩点后,新图是一棵树,树的边就是原无向图桥. 现在问题转化为:在树中至少添加多少条边能使图变为双连通图. 结论:添加边数=(树中度为1的节点数+1)/2 具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上

poj 3352 Road Construction【边双连通求最少加多少条边使图双连通&amp;&amp;缩点】

Road Construction Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 10141   Accepted: 5031 Description It's almost summer time, and that means that it's almost summer construction time! This year, the good people who are in charge of the r

Bridges Gym - 100712H ? 无向图的边双连通分量,Tarjan缩点

http://codeforces.com/gym/100712/attachments 题意是给定一个无向图,要求添加一条边,使得最后剩下的桥的数量最小. 注意到在环中加边是无意义的. 那么先把环都缩成一个点,然后重新建立一颗树,找出树的直径就好. #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <algorithm> #incl

Road Construction POJ - 3352 (边双连通分量)

Road Construction POJ - 3352 题意:一个无向图(无重边),问至少还要加多少边使得去掉任意一条边后任意两点仍可互达. 无向图的边双连通分量(无重边) 先用一次dfs标记出割边,然后dfs标记出各联通分量 再根据割边,缩点重新建图,生成一颗树 则答案就是(叶子树+1)/2. 1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <cstring

POJ - 3352 Road Construction(边双连通分量)

题目大意:给出一张无向图,问添加多少边才能使得这张无向图变成边双连通分量 解题思路:先求出所有的边双连通分量,再将边双连通缩成一个点,通过桥连接起来,这样就形成了一棵无根树了 现在的问题是,将这颗无根树变成边双连通分量 网上的解释是:统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf.则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2.具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径