无向图的双连通分量——AcWing 395. 冗余路径

news2024/9/21 4:36:24

无向图的双连通分量

定义

在无向图中,一个双连通分量(Biconnected Component, BCC)是指这样的子图:删除其中任意一个顶点都不会使这个子图分离成两个或更多个不相连的子图。换句话说,双连通分量是无割点的极大连通子图。

运用情况

  • 网络可靠性分析:在通信网络中,理解网络的连通性,找出关键的节点和链路,有助于提升网络的稳定性和容错能力。

  • 电路设计:在电路板设计中,识别哪些部分是双连通的可以帮助工程师优化布局,减少因单一故障点导致的整体失效风险。

  • 社交网络分析:在社交网络中,双连通分量可以帮助识别社群内部的关键人物或纽带,这些人物对于社群的凝聚力至关重要。

注意事项

  1. 割点:双连通分量的计算过程中,割点(即删除该点会使得图分裂成多个连通分量的点)的识别非常重要。每个割点都是连接不同双连通分量的桥梁。

  2. 边的冗余:双连通分量中的每条边至少属于一个双连通分量,但可能同时属于多个分量,尤其是那些连接割点的边。

  3. 数据结构:在算法实现时,合理选择数据结构(如邻接表、并查集等)对提高算法效率至关重要。

解题思路

  1. DFS遍历:通过深度优先搜索(DFS)遍历图中的所有顶点,为每个顶点分配发现时间和低值(low value),类似于Tarjan算法中寻找强连通分量的过程。

  2. 识别割点和双连通分量

    • 对于每个顶点u,在其DFS子树中,如果存在一个子节点v,使得v到其所有祖先的路径都经过u,则u是一个割点。
    • 当发现一个顶点u的低值等于其发现时间时,意味着找到了一个双连通分量的边界。
  3. 记录双连通分量:每当找到一个双连通分量的边界,就记录当前DFS栈中的顶点集合作为该双连通分量的一部分。

AcWing 395. 冗余路径

题目描述

395. 冗余路径 - AcWing题库

运行代码

#include <cstring>
#include <iostream>

using namespace std;

const int N = 5010, M = 20010;

int n, m;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp;
int stk[N], top;
int id[N], dcc_cnt;
int d[N];
bool is_bridge[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void tarjan(int u, int from)
{
    dfn[u] = low[u] = ++ timestamp;
    stk[ ++ top] = u;
    
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(!dfn[j])
        {
            tarjan(j, i);
            low[u] = min(low[u], low[j]);
            if(dfn[u] < low[j]) 
                is_bridge[i] = is_bridge[i ^ 1] = true;
        }
        else if(i != (from ^ 1))
            low[u] = min(low[u], dfn[j]);
    }
    
    if(dfn[u] == low[u])
    {
        ++ dcc_cnt;
        int y;
        do
        {
            y = stk[top -- ];
            id[y] = dcc_cnt;
        }while(u != y);
    }
}

int main()
{
    while(cin >> n >> m, n || m)
    {
        memset(h, -1, sizeof h);
        idx = 0;
        
        while(m -- )
        {
            int a, b;
            cin >> a >> b;
            add(a, b);
            add(b, a);
        }
        
        tarjan(1, -1);
        
        for(int i = 0; i < idx; i ++ )
            if(is_bridge[i])
                d[id[e[i]]] ++ ;
        
        int cnt = 0;
        for(int i = 1; i <= dcc_cnt; i ++ )
            if(d[i] == 1) 
                cnt ++ ;
        
        cout << (cnt + 1) / 2 << endl;
        
        return 0;
    }
}

代码思路

  • 首先定义了一些常量和数组来存储图的相关信息,如节点数、边数、邻接表等。
  • add 函数用于向邻接表中添加边。
  • tarjan 函数是核心,通过深度优先搜索来计算每个节点的 dfn(时间戳)和 low(回溯能到达的最早时间戳)值,从而判断边是否为桥,并标记双连通分量。
  • 在 main 函数中,读取输入的节点数和边数,构建图,调用 tarjan 函数进行计算,最后统计双连通分量的相关信息并输出结果。

改进思路

  • 代码的注释可以更加详细,以提高代码的可理解性。
  • 可以增加一些错误处理,比如输入不合法时的提示。
  • 考虑使用更具可读性的数据结构,如 vector 来替代数组。

改进代码

#include <iostream>
#include <vector>

using namespace std;

// 最大节点数
const int N = 5010; 
// 最大边数
const int M = 20010; 

int n, m;
// 邻接表
vector<int> h(N, -1);
vector<int> e(M);
vector<int> ne(M);
int idx;

// 时间戳
int dfn[N];
// 回溯能到达的最早时间戳
int low[N];
int timestamp;

// 栈
vector<int> stk;
int top;

// 每个节点所属的双连通分量编号
int id[N];
int dcc_cnt;

// 每个双连通分量的度
int d[N];
// 标记边是否为桥
bool is_bridge[M];

// 添加边
void add(int a, int b) {
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

/**
 * Tarjan 算法核心函数
 * @param u 当前节点
 * @param from 边的编号
 */
void tarjan(int u, int from) {
    dfn[u] = low[u] = ++timestamp;
    stk.push_back(u);

    for (int i = h[u]; i!= -1; i = ne[i]) {
        int j = e[i];
        if (!dfn[j]) {
            tarjan(j, i);
            low[u] = min(low[u], low[j]);
            if (dfn[u] < low[j]) 
                is_bridge[i] = is_bridge[i ^ 1] = true;
        }
        else if (i!= (from ^ 1))
            low[u] = min(low[u], dfn[j]);
    }

    if (dfn[u] == low[u]) {
        ++dcc_cnt;
        int y;
        do {
            y = stk.back();
            stk.pop_back();
            id[y] = dcc_cnt;
        } while (u!= y);
    }
}

/**
 * 主函数
 * 处理输入,计算并输出结果
 */
int main() {
    while (cin >> n >> m, n || m) {
        // 初始化
        fill(h.begin(), h.end(), -1);
        idx = 0;

        while (m--) {
            int a, b;
            cin >> a >> b;
            add(a, b);
            add(b, a);
        }

        tarjan(1, -1);

        for (int i = 0; i < idx; i++)
            if (is_bridge[i])
                d[id[e[i]]]++;

        int cnt = 0;
        for (int i = 1; i <= dcc_cnt; i++)
            if (d[i] == 1) 
                cnt++;

        cout << (cnt + 1) / 2 << endl;

        return 0;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1922057.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

lua 脚本语言 : 基础到高级语法

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…

[Python学习篇] Python异常

什么是异常&#xff1f; 异常&#xff08;Exception&#xff09;是指在程序执行过程中发生的错误事件&#xff0c;它会中断程序的正常执行流程。异常可以由程序中的错误引发&#xff0c;也可以通过主动抛出异常来处理特殊情况。Python 使用异常处理机制来捕获和处理这些错误&am…

初识c++(构造函数,析构函数,拷贝构造函数,赋值运算符重载)

一、类的默认函数 默认成员函数就是用户没有显式实现&#xff0c;编译器会自动生成的成员函数称为默认成员函数。 #include<iostream> using namespace std; class Date { public:Date(){_year 1;_month 1;_day 1;cout << _year << "/" <&…

日常的学习

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Android ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 正文 7.11 resAndroidManifest 笔记 <> <> selector shape resources main下的AndroidMainifest.xml文件 application …

sql注入时间盲注

基于时间的盲注 也叫延时注入。通过观察页面&#xff0c;既没有回显数据库内容&#xff0c;又没有报错信息也没有布尔类型状态&#xff0c;那么我们可以考虑用“绝招”--延时注入。延时注入就是根据页面的响应时间来判断是否存在注入&#xff0c;一点一点注入出数据库的信息。我…

【进阶】利用python内置模块自动化发送邮件及邮件附件

目录 自动化发送邮件 流程&#xff1a; 步骤&#xff1a; 【重点】 【MIMEText--发送文本类型的邮件】 【MIMEImage-发送附件为图片的邮件】 【MIMEBase--发送附件为html报告的邮件】 自动化发送邮件 以qq邮箱为例&#xff0c;提前打开POP3/IMAP/SMTP/Exchange/CardDAV 服…

【web]-信息收集-空白页面

打开是一张图 查看源码&#xff0c;发现就一个链接是有用信息&#xff0c;用目录扫描工具&#xff0c;没有发现有价值的信息。 F12&#xff0c;查看请求和相应信息&#xff0c;在响应头中发现了信息。 还有一个小技巧&#xff1a;点击手机图标&#xff0c;可以切换到手机模式中…

Web浏览器485通讯读取RFID卡号js JavaScript

本示例使用设备&#xff1a;485通讯液显带键盘RFID打菲计件读卡器工位机串口可二次开发编程-淘宝网 (taobao.com) <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> …

计组_总线

2024.06.21&#xff1a;计算机组成原理总线学习笔记 第23节 总线 3.1 总线的基本概念&#xff08;联想数据通路&#xff09;3.2 总线的分类3.2.1 片内总线&#xff08;CPU芯片内部的总线&#xff09;3.2.2 系统总线3.2.3 通信总线&#xff08;跨系统&#xff0c;408一般不考&am…

四个“一体化”——构建数智融合时代下的一站式大数据平台

随着智能化技术的飞速发展&#xff0c;尤其是以生成式AI为代表的技术快速应用&#xff0c;推动了数据与智能的深化融合&#xff0c;给数据基础设施带来了新的变革和挑战。如何简化日益复杂的系统架构&#xff0c;提高数据处理效率&#xff0c;降低开发运维成本&#xff0c;促进…

十、(正点原子)Linux阻塞和非阻塞IO

阻塞和非阻塞 IO 是 Linux 驱动开发里面很常见的两种设备访问模式&#xff0c;在编写驱动的时候一定要考虑到阻塞和非阻塞。这里的“IO”并不是我们学习 STM32 或者其他单片机的时候所说的“GPIO”(也就是引脚)。这里的 IO 指的是 Input/Output&#xff0c;也就是输入/输出&…

matlab支持向量机使用错误

&#x1f3c6;本文收录于《CSDN问答解答》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&…

使用Qt和mitmproxy开发一个抓取网页短视频的万能工具

目录 实现原理 mitmproxy介绍 功能简介 安装 脚本示例 如何使用 解释 注意事项 QT工具实现 其他资源 实现原理 使用WebView组件造一工具,工具可输入网页地址并显示网页内容及播放视频。把工具的代理设置指向mitmproxy的端口服务。配合使用mitmproxy的MITM技术,监…

MySql性能调优03-[SQL优化]

SQL优化 MySQL优化SQL优化-不要写select *SQL优化-小表驱动大表&#xff0c;而不是大表驱动小表SQL优化-连接查询代替子查询SQL优化-提升group by的效率 MySQL优化 trace工具 set session optimizer_traceenabledon,end_markers_in_json on; -- 开启trace select * From emplo…

指针详解(2)

指针详解(2) 对数组名的理解 在C语言里数组名还表示着数组首元素地址。 int arr[5] {1, 2, 3, 4, 5}; int* p &arr[0]; int* p arr;以上这两种&#xff0c;对指针p进行赋值的操作均是等价的&#xff0c;都将数组首元素的地址赋给指针p。 不妨&#xff0c;我们可以测…

【C++进阶学习】第六弹——set和map——体会用C++来构建二叉搜索树

set和map基础&#xff1a;【C进阶学习】第五弹——二叉搜索树——二叉树进阶及set和map的铺垫-CSDN博客 前言&#xff1a; 在上篇的学习中&#xff0c;我们已经学习了如何使用C语言来实现二叉搜索树&#xff0c;在C中&#xff0c;我们是有现成的封装好的类模板来实现二叉搜索树…

SpringBoot新手快速入门系列教程六:基于MyBatis的一个简单Mysql读写例子

我的教程都是亲自测试可行才发布的&#xff0c;如果有任何问题欢迎留言或者来群里我每天都会解答。 MyBatis和JPA是两种不同的Java持久层框架&#xff0c;各有其优缺点。以下是它们的比较&#xff1a; MyBatis 优点 灵活性高&#xff1a;MyBatis允许手动编写SQL查询&#xf…

AWDAWFAAFAWAWFAWF

创建两张表&#xff1a;部门&#xff08;dept&#xff09;和员工&#xff08;emp&#xff09; 创建视图v_emp_dept_id_1&#xff0c;查询销售部门的员工姓名和家庭住址 创建视图v_emp_dept&#xff0c;查询销售部门员工姓名和家庭住址及部门名称 创建视图v_dept_emp_count(dept…

Ubuntu: gitee免密

安装git sudo apt-get install git下载 git clone XXX SSH keys 第一步&#xff1a;检查本地是否有 SSH Key存在 ls -al ~/.ssh第二步&#xff1a;配置你注册的邮箱 ssh-keygen -t rsa -C "your_emailexample.com"输入命令后一直回车 第三步&#xff1a;获取公钥…

乐观锁原理

乐观锁是一种并发控制的方法&#xff0c;主要用于多线程环境下&#xff0c;用于保证数据的一致性。其核心思想是&#xff1a;"在多个事务中乐观地读取数据&#xff0c;在提交时再验证是否有冲突&#xff0c;如果没有&#xff0c;则提交&#xff1b;如果有&#xff0c;则回…