首页 > 代码库 > 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
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 无向图的边-双连通分量(无重边 & 重边)
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。