热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

双连通分量模板以及对一些不好理解点的解释

双连通分量(biconnectedcomponent,简称bcc)概念:双连通分量有点双连通分量和边双连通分量两种。若一个无向图中的去掉任意一个节点(

双连通分量(biconnected component, 简称bcc)概念:

双连通分量有点双连通分量和边双连通分量两种。若一个无向图中的去掉任意一个节点(一条边)都不会改变此图的连通性,即不存在割点(桥),则称作点(边)双连通图。一个无向图中的每一个极大点(边)双连通子图称作此无向图的点(边)双连通分量。求双连通分量可用Tarjan算法。--百度百科


先学一下tarjan算法以及求割点割边的算法之后,再看会比较好理解一些。(1.Tarjan 2.图的割点割边)


先看比较难写点双连通分量的求法,直接看代码理解。另附图一张:


Code:

#include
using namespace std;
const int maxn = 110;
const int maxm = 10010;
struct node
{
int u, v, next;
}edge[maxm], tp;
int n, m; //点数,边数
int head[maxn], no;
int add_bcc[maxn];//去掉该点之后能增加的bcc数目
int index; //时间戳
int yltd; //图的初始连通分量
int num[maxn], low[maxn];//时间戳和能回到的最早时间戳
int iscut[maxn];//是否为割点
int bccno[maxn], bcc_cnt; //bccno[i]表示i属于哪个bcc
stack S; //存储bcc边
vector bcc[maxn];
inline void init()
{
no = 0;
memset(head, -1, sizeof head);
}
inline void add(int u, int v)
{
edge[no].u = u; edge[no].v = v;
edge[no].next = head[u]; head[u] = no++;
edge[no].u = v; edge[no].v = u;
edge[no].next = head[v]; head[v] = no++;
}
inline void input()
{
int u, v;
for(int i &#61; 1; i <&#61; m; &#43;&#43;i)
{
scanf("%d %d", &u, &v);
add(u, v);
}
}
void tarjan(int cur, int father)
{
int child &#61; 0;
num[cur] &#61; low[cur] &#61; &#43;&#43;index;
int k &#61; head[cur];
while(k !&#61; -1)
{
int v &#61; edge[k].v;
if(!num[v])
{
S.push(edge[k]);
&#43;&#43;child;
tarjan(v, cur);
low[cur] &#61; min(low[cur], low[v]);
if(low[v] >&#61; num[cur])
//把更节点看做普通的节点&#xff0c;对根节点这个条件是一定满足的&#xff0c;
//可以实现把回溯到根节点剩下的出栈&#xff0c;其实这就是一个新的双连通分量
{
iscut[cur] &#61; 1;
&#43;&#43;add_bcc[cur];
&#43;&#43;bcc_cnt;//准备把新的双连通分量加入bcc
bcc[bcc_cnt].clear();
while(true)
{
tp &#61; S.top(); S.pop();
if(bccno[tp.u] !&#61; bcc_cnt)
{
bcc[bcc_cnt].push_back(tp.u);
bccno[tp.u] &#61; bcc_cnt;
}
if(bccno[tp.v] !&#61; bcc_cnt)
{
bcc[bcc_cnt].push_back(tp.v);
bccno[tp.v] &#61; bcc_cnt;
}
if(tp.u &#61;&#61; edge[k].u && tp.v &#61;&#61; edge[k].v) break;
}
}
}
else if(num[v] {
//num[v] //访问过了。然后再从cur访问&#xff0c;如果不判断就会将这个点加入S&#xff0c;造成错误&#xff0c;见上图。
//可以看到时间戳走到6再次回溯到2时&#xff0c;还能通过2对2-4这条边进行一次尝试&#xff0c;不判断的话4会被加到S
S.push(edge[k]);
low[cur] &#61; min(low[cur], num[v]);
}
k &#61; edge[k].next;
}
if(father <0)
{
//把根节点看做普通节点了&#xff0c;所以下面最后的特殊判断必需。
if(child > 1) iscut[cur] &#61; 1, add_bcc[cur] &#61; child-1;
else iscut[cur] &#61; 0, add_bcc[cur] &#61; 0;
}
}
void Find_Cut(int l, int r)
{
index &#61; bcc_cnt &#61; yltd &#61; 0;
memset(add_bcc, 0, sizeof add_bcc);
memset(num, 0, sizeof num);
memset(iscut, 0, sizeof iscut);
memset(bccno, 0, sizeof bccno);
memset(low, 0, sizeof low);
for(int i &#61; l; i <&#61; r; &#43;&#43;i)
{
if(!num[i]) tarjan(i, -1), &#43;&#43;yltd;
}
}
void PutAll(int l, int r)
{
for(int i &#61; l; i <&#61; r; &#43;&#43;i)
{
if(iscut[i]) printf("%d是割点&#xff0c;", i);
printf("去掉点%d之后有%d个双连通分量\n", i, add_bcc[i]&#43;yltd);
}
}
void PutBcc()
{
printf("有%d个BCC\n", bcc_cnt);
for(int i &#61; 1; i <&#61; bcc_cnt; &#43;&#43;i)
{
printf("BCC%d有%d个点: ", i, bcc[i].size());
for(int j &#61; 0; j printf("\n");
}
}
int main()
{
while(~scanf("%d %d", &n, &m))
{
init();
input();
Find_Cut(1, n);
PutAll(1, n);
PutBcc();
}
return 0;
}
/*
测试样例&#xff1a;
8 11
1 2
2 3
3 4
2 4
2 5
2 6
5 6
1 7
1 8
7 8
2 8
*/


上面仔细模拟模拟就能想通(例题&#xff1a;
UVALive 5135
)。


双连通分量的求法就比较朴素了。

Code&#xff1a;

#include
using namespace std;
const int maxn &#61; 110;
const int maxm &#61; 10010;
struct node
{
int u, v, next;
}edge[maxm];
int n, m; //点数&#xff0c;边数
int head[maxn], no;
int index; //时间戳
int num[maxn], low[maxn];//时间戳和能回到的最早时间戳
int iscutedge[maxm];//是否为割边&#xff0c;存邻接表的索引
inline void init()
{
no &#61; 0;
memset(head, -1, sizeof head);
}
inline void add(int u, int v)
{
edge[no].u &#61; u; edge[no].v &#61; v;
edge[no].next &#61; head[u]; head[u] &#61; no&#43;&#43;;
edge[no].u &#61; v; edge[no].v &#61; u;
edge[no].next &#61; head[v]; head[v] &#61; no&#43;&#43;;
}
inline void input()
{
int u, v;
for(int i &#61; 1; i <&#61; m; &#43;&#43;i)
{
scanf("%d %d", &u, &v);
add(u, v);
}
}
void tarjan(int cur, int father)
{
num[cur] &#61; low[cur] &#61; &#43;&#43;index;
int k &#61; head[cur];
while(k !&#61; -1)
{
int v &#61; edge[k].v;
if(!num[v])
{
tarjan(v, cur);
low[cur] &#61; min(low[cur], low[v]);
if(low[v] > num[cur])
{
//把割边的两个方向的边都标记
iscutedge[k] &#61; iscutedge[k^1] &#61; 1;
}
}
else if(edge[k].v !&#61; father)
{
low[cur] &#61; min(low[cur], num[v]);
}
k &#61; edge[k].next;
}
}
//找出割边标记上
void Find_CutEdge(int l, int r)
{
index &#61; 0;
memset(iscutedge, 0, sizeof iscutedge);
memset(num, 0, sizeof num);
memset(low, 0, sizeof low);
for(int i &#61; l; i <&#61; r; &#43;&#43;i)
{
if(!num[i]) tarjan(i, -1);
}
}
int dfs(int cur)
{
num[cur] &#61; 1;
int flag &#61; 0; //判断是否存在边双联通分量&#xff0c;以免多输出换行
for(int k &#61; head[cur]; k !&#61; -1; k &#61; edge[k].next)
{
if(iscutedge[k]) continue;
flag &#61; 1;
iscutedge[k] &#61; iscutedge[k^1] &#61; 1;
printf("(%d, %d) ", cur, edge[k].v);
if(!num[edge[k].v]) dfs(edge[k].v);
}
return flag;
}
//dfs输出就能得到相应的双连通分量
void PutBccEdge(int l, int r)
{
memset(num, 0, sizeof num);
printf("双连通分量的边有&#xff1a;\n");
for(int i &#61; l; i <&#61; r; i&#43;&#43;)
if(!num[i])
{
if(dfs(i)) cout < }
}
int main()
{
while(~scanf("%d %d", &n, &m))
{
init();
input();
Find_CutEdge(1, n);
PutBccEdge(1, n);
}
return 0;
}
/*
测试样例&#xff1a;
8 10
1 2
2 3
3 4
2 4
2 5
2 6
5 6
1 7
1 8
7 8
*/

找完割边然后进行DFS输出所有边双连通分量所包含的边~(例题&#xff1a;poj 3352)。


Code2(正确模板方法):

#include
using namespace std;
const int maxn &#61; 25005;
const int maxm &#61; 1e5&#43;5;
struct node {
int u, v, next;
} edge[maxm];
int no, head[maxn];
int idx, dfn[maxn], low[maxn];
int top, S[maxn];
int bcc_cnt, cut;
int bccno[maxn];
vector bcc[maxn];
int n, m;
void init()
{
no &#61; 0;
memset(head, -1, sizeof head);
}
void add(int u, int v)
{
edge[no].u &#61; u; edge[no].v &#61; v;
edge[no].next &#61; head[u]; head[u] &#61; no&#43;&#43;;
}
void tarjan(int u, int fa)
{
dfn[u] &#61; low[u] &#61; &#43;&#43;idx;
S[&#43;&#43;top] &#61; u;
for(int k &#61; head[u]; k&#43;1; k &#61; edge[k].next)
{
int v &#61; edge[k].v;
if(!dfn[v])
{
tarjan(v, u);
low[u] &#61; min(low[u], low[v]);
if(low[v] > dfn[u])
{
&#43;&#43;cut; //割边&#43;1
}
}
else if(v !&#61; fa)
{
low[u] &#61; min(low[u], dfn[v]);
}
}
if(dfn[u] &#61;&#61; low[u])
{
&#43;&#43;bcc_cnt; //边双连通分量&#43;1
do
{
bcc[bcc_cnt].push_back(S[top]);
bccno[S[top]] &#61; bcc_cnt;
--top;
}
while(S[top&#43;1] !&#61; u);
}
}
void work()
{
memset(dfn, 0, sizeof dfn);
memset(bccno, 0, sizeof bccno);
idx &#61; top &#61; bcc_cnt &#61; cut &#61; 0;
for(int i &#61; 1; i <&#61; n; &#43;&#43;i)
if(!dfn[i]) tarjan(i, i);
for(int i &#61; 1; i <&#61; bcc_cnt; &#43;&#43;i)
{
cout < for(int j &#61; 0; j cout < cout < }
}
int main()
{
init();
cin >> n >> m;
for(int i &#61; 1; i <&#61; m; &#43;&#43;i)
{
int u, v;
cin >> u >> v;
add(u, v); add(v, u);
}
work();
return 0;
}
/*
input&#xff1a;
6 7
1 2
1 3
2 3
3 4
4 5
4 6
5 6
output&#xff1a;
1: 5 6 4
2: 2 3 1
*/


之前的错误理解&#xff1a;tarjan之后图中low值相同的两个点必定在同一个边双连通分量中。

上述说法是错误的&#xff0c;上述代码做法是正确的。


继续加油~



推荐阅读
  • 本文探讨了如何在模运算下高效计算组合数C(n, m),并详细介绍了乘法逆元的应用。通过扩展欧几里得算法求解乘法逆元,从而实现除法取余的计算。 ... [详细]
  • Codeforces Round #566 (Div. 2) A~F个人题解
    Dashboard-CodeforcesRound#566(Div.2)-CodeforcesA.FillingShapes题意:给你一个的表格,你 ... [详细]
  • 本题探讨如何通过最大流算法解决农场排水系统的设计问题。题目要求计算从水源点到汇合点的最大水流速率,使用经典的EK(Edmonds-Karp)和Dinic算法进行求解。 ... [详细]
  • 火星商店问题:线段树分治与持久化Trie树的应用
    本题涉及编号为1至n的火星商店,每个商店有一个永久商品价值v。操作包括每天在指定商店增加一个新商品,以及查询某段时间内某些商店中所有商品(含永久商品)与给定密码值的最大异或结果。通过线段树分治和持久化Trie树来高效解决此问题。 ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 题目Link题目学习link1题目学习link2题目学习link3%%%受益匪浅!-----&# ... [详细]
  • 题目描述:给定n个半开区间[a, b),要求使用两个互不重叠的记录器,求最多可以记录多少个区间。解决方案采用贪心算法,通过排序和遍历实现最优解。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • 本文探讨了如何在给定整数N的情况下,找到两个不同的整数a和b,使得它们的和最大,并且满足特定的数学条件。 ... [详细]
  • Splay Tree 区间操作优化
    本文详细介绍了使用Splay Tree进行区间操作的实现方法,包括插入、删除、修改、翻转和求和等操作。通过这些操作,可以高效地处理动态序列问题,并且代码实现具有一定的挑战性,有助于编程能力的提升。 ... [详细]
  • 本文探讨了 C++ 中普通数组和标准库类型 vector 的初始化方法。普通数组具有固定长度,而 vector 是一种可扩展的容器,允许动态调整大小。文章详细介绍了不同初始化方式及其应用场景,并提供了代码示例以加深理解。 ... [详细]
  • 本教程涵盖OpenGL基础操作及直线光栅化技术,包括点的绘制、简单图形绘制、直线绘制以及DDA和中点画线算法。通过逐步实践,帮助读者掌握OpenGL的基本使用方法。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • C++: 实现基于类的四面体体积计算
    本文介绍如何使用C++编程语言,通过定义类和方法来计算由四个三维坐标点构成的四面体体积。文中详细解释了四面体体积的数学公式,并提供了两种不同的实现方式。 ... [详细]
  • 本文详细介绍了C语言中链表的两种动态创建方法——头插法和尾插法,包括具体的实现代码和运行示例。通过这些内容,读者可以更好地理解和掌握链表的基本操作。 ... [详细]
author-avatar
276443071_7309cb
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有