第三章 图论 No.10无向图的双连通分量

news2025/1/13 15:42:46

文章目录

    • 定义
    • Tarjan求e-DCC
    • Tarjan求v-DCC
      • 395. 冗余路径
      • 1183. 电力
      • 396. 矿场搭建

定义

无向图有两种双连通分量

  1. 边双连通分量,e-DCC
  2. 点双连通分量,v-DCC

桥:删除这条无向边后,图变得不连通,这条边被称为桥
边双连通分量:极大的不含有桥的连通区域,说明无论删除e-DCC中的哪条边,e-DCC依旧连通 (该连通分量的任意边属于原图中的某条环)。此外,任意两点之间一定包含两条不相交(无公共边)的路径

割点:删除该点(与该点相关的边)后,图变得不连通,这个点被称为割点
点双连通分量:极大的不含有割点的连通区域

一些性质:

  1. 每个割点至少属于两个连通分量
  2. 任何两个割点之间的边不一定是桥,任何桥两边的端点不一定是割点,两者没有必然联系,一个点连通分量也不一定是边连通分量

image.png

image.png


Tarjan求e-DCC

无向图不存在横叉边
和有向图的强连通分量类似,引入dfn和low两个数组
如何找到桥?判断x->y的y是否能走到x之前(祖先节点),如果能走到,x和y在一个环中,删除这条边,其他点依然是连通的
所以x->y为桥:dfn[x] < low[y]

如何找到所有边的双连通分量?

  1. 删除所有桥
  2. 或者用stk辅助,若dfn[x] == low[x],说明x出发一定走不到x的祖先节点,那么x和其父节点之间的边是桥,此时还在stk中的点为e-DCC的节点

这里使用第二种方式,模板:

void tarjan(int x, int from)
{
    dfn[x] = low[x] = ++ tp;
    stk[ ++ tt] = x;
    for (int i = h[x]; i != -1; i = ne[i])
    {
        int y = e[i];
        if (!dfn[y])
        {
            tarjan(y, i);
            low[x] = min(low[x], low[y]);
        }
        else if (i != (from ^ 1))
            low[x] = min(low[x], dfn[y]);
        if (dfn[x] < low[y])
                st[i] = st[i ^ 1] = true;
    }
    if (dfn[x] == low[x])
    {
        int y;
        cnt ++ ;
        do {
            y = stk[tt -- ];
            id[y] = cnt;
        } while (x != y);
    }
}

由于无向图要存储两条有向边,并且从数组的0下标开始存储,所以0,1、2,3…这样一对的边是互相反向的边,即i和i ^ 1为反向边
为什么与有向图的强连通分量不同,边双连通分量不需要使用st数组以标记栈中的元素?
因为无向图不存在横叉边的概念,就不会出现:x->y而y的dfn小于x,因为在无向图中y一定会向x遍历


Tarjan求v-DCC

如何求割点?low[y] >= dfn[x],删除x节点后,y就是一颗独立的子树

  1. 如果x不是根节点,那么x是一个割点
  2. 如果x是根节点,至少有两个y满足以上关系

求割点的板子:

void tarjan(int x)
{
    dfn[x] = low[x] = ++ tp;
    int t = 0;
    for (int i = h[x]; i != -1; i = ne[i])
    {
        int y = e[i];
        if (!dfn[y])
        {
            tarjan(y);
            low[x] = min(low[x], low[y]);
            if (low[y] >= dfn[x]) t ++ ;
        }
        else low[x] = min(low[x], dfn[y]);
    }
    
    if (x != root) t ++ ;
    ans = max(ans, t);
}

将每个v-DCC向其包含的割点连一条边
缩点后,边的数量不会增加,点的数量可能会增加到两倍
image.png

tarjan求v-DCC与割点的板子:
一个孤立的点也是一个v-DCC,这里需要特判
若满足条件:low[y] >= dfn[x],那么y的子树与x一起就是一个v-DCC
对于该节点是否是割点还需要特判,若该节点为根,并且与该节点相连的连通块数量为1,那么该节点就不是一个割点

void tarjan(int x)
{
	dfn[x] = low[x] = ++ tp;
	stk[ ++ tt ] = x;

	if (x == root && h[x] == -1)
	{
		++ dcnt;
		dcc[dcnt].push_back(x);
		return;
	}
	int t = 0; // 与x相连的连通块数量
	for (int i = h[x]; i != -1; i = ne[i])
	{
		int y = e[i];
		if (!dfn[y])
		{
			tarjan(y);
			low[x] = min(low[x], low[y]);
			if (low[y] >= dfn[x])
			{
				dcnt ++, t ++ ;
				if (x != root || t > 1) st[x] = true; // x为割点
				int u;
				do {
					u = stk[tt -- ];
					dcc[dcnt].push_back(u);
				} while (u != y);
				dcc[dcnt].push_back(x);
			}
		}
		else low[x] = min(low[x], dfn[y]);
	}
}

395. 冗余路径

395. 冗余路径 - AcWing题库
image.png

任意两点间都存在两条没有重复边的路径,等价于整个图是一个双连通分量

将无向连通图的边双连通分量缩点后,得到的结构是一颗树,因为边双连通分量是不包含桥的结构,缩点后,图中只含有桥,即删除任意一条边后,图成为两个连通块,这是一个树结构

为了满足题意,需要向这颗树中添加边,使之成为边连通分量,那么要加几条边?
image.png

连接所有叶子节点,使这颗树结构成为双连通分量,至少需要加 [ ( c n t + 1 ) / 2 ] [(cnt + 1) / 2] [(cnt+1)/2]然后再下取整的边数,也就是将每个叶子节点相连,使环满足双连通分量的性质

注意cnt为1时需要特判

#include <iostream>
#include <cstring>
using namespace std;

const int N = 5010, M = 10010;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], tp, cnt;
int stk[N], tt, id[N];
bool st[N]; int d[N];
int n, m;

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

void tarjan(int x, int from)
{
    dfn[x] = low[x] = ++ tp;
    stk[ ++ tt] = x;
    for (int i = h[x]; i != -1; i = ne[i])
    {
        int y = e[i];
        if (!dfn[y])
        {
            tarjan(y, i);
            low[x] = min(low[x], low[y]);
            if (dfn[x] < low[y])
                st[i] = st[i ^ 1] = true;
        }
        else if (i != (from ^ 1))
            low[x] = min(low[x], dfn[y]);
    }
    if (dfn[x] == low[x])
    {
        int y;
        cnt ++ ;
        do {
            y = stk[tt -- ];
            id[y] = cnt;
        } while (x != y);
    }
}

int main()
{
    memset(h, -1, sizeof(h));
    scanf("%d%d", &n, &m);
    int x, y;
    for (int i = 0; i < m; ++ i )
    {
        scanf("%d%d", &x, &y);
        add(x, y), add(y, x);
    }
    
    tarjan(1, -1);
    for (int i = 0; i < idx; i ++ )
        if (st[i])
            d[id[e[i]]] ++ ;
    
    int res = 0;
    for (int i = 1; i <= cnt; ++ i )
        if (d[i] == 1) res ++ ;
        
    if (cnt == 1) puts("0");
    else printf("%d\n", (res + 1) / 2);
    return 0;
}

debug:^的优先级小于!=

可以不使用st数组标记桥:

#include <iostream>
#include <cstring>
using namespace std;

const int N = 5010, M = 10010;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], tp, cnt;
int stk[N], tt, id[N];
int d[N];
int n, m;

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

void tarjan(int x, int from)
{
    dfn[x] = low[x] = ++ tp;
    stk[ ++ tt] = x;
    for (int i = h[x]; i != -1; i = ne[i])
    {
        int y = e[i];
        if (!dfn[y])
        {
            tarjan(y, i);
            low[x] = min(low[x], low[y]);
        }
        else if (i != (from ^ 1))
            low[x] = min(low[x], dfn[y]);
    }
    if (dfn[x] == low[x])
    {
        int y;
        cnt ++ ;
        do {
            y = stk[tt -- ];
            id[y] = cnt;
        } while (x != y);
    }
}

int main()
{
    memset(h, -1, sizeof(h));
    scanf("%d%d", &n, &m);
    int x, y;
    for (int i = 0; i < m; ++ i )
    {
        scanf("%d%d", &x, &y);
        add(x, y), add(y, x);
    }
    
    tarjan(1, -1);
    
    
    for (int x = 1; x <= n; ++ x )
        for (int i = h[x]; i != -1; i = ne[i])
        {
            int y = e[i];
            int a = id[x], b = id[y];
            if (a != b) d[a] ++ ;
        }
        
    int res = 0;
    for (int i = 1; i <= cnt; i ++ )
        if (d[i] == 1) res ++ ;
    
    if (cnt == 1) puts("0");
    else printf("%d\n", (res + 1) / 2);
    return 0;
}

1183. 电力

1183. 电力 - AcWing题库
image.png

枚举所有割点,判断删除哪个割点后剩余的连通块数量最大
剩余的连通块数量为ans + cnt - 1
由于题目给定的图并不是一个连通图,所以可能存在多个连通块,cnt为连通块数量
枚举所有割点只能在一个连通块中枚举,此时其他连通块的数量为cnt - 1
又因为ans为删除割点后,剩余连通块最多的值,所以答案为ans + cnt - 1

这题的点编号从0开始

#include <iostream>
#include <cstring>
using namespace std;

const int N = 10010, M = 30010;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], tp, cnt;
int ans, n, m, root;

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

void tarjan(int x)
{
    dfn[x] = low[x] = ++ tp;
    int t = 0;
    for (int i = h[x]; i != -1; i = ne[i])
    {
        int y = e[i];
        if (!dfn[y])
        {
            tarjan(y);
            low[x] = min(low[x], low[y]);
            if (low[y] >= dfn[x]) t ++ ;
        }
        else low[x] = min(low[x], dfn[y]);
    }
    
    if (x != root) t ++ ;
    ans = max(ans, t);
}

int main()
{
    while (scanf("%d%d", &n, &m), n | m)
    {
        memset(h, -1, sizeof(h));
        memset(dfn, 0, sizeof(dfn));
        idx = tp = cnt = ans = 0;
        int x, y;
        for (int i = 0; i < m; ++ i )
        {
            scanf("%d%d", &x, &y);
            add(x, y), add(y, x);
        }
        for (root = 0; root < n; ++ root)
        {
            if (!dfn[root]) 
            {
                cnt ++ ;
                tarjan(root);
            }
        }
        printf("%d\n", ans + cnt - 1);
    }
    return 0;
}

debug:dfn数组没有置空


396. 矿场搭建

396. 矿场搭建 - AcWing题库
image.png

image.png
对于图中的每个连通块,分情况讨论:

  1. 若连通块无割点,那么任意设置两个救援点即可
  2. 若连通块中有割点,缩点:将每个割点依然看成一个点,将每个v-DCC向其包含的割点连线
  • 缩点后得到一棵树,对于叶子节点,需要建立救援点。因为只有一个点与其相连,若该点坍塌,需要在内部建立救援点。假设内部节点数量为cnt,方案数为cnt-1个,去除割点
  • 对于非叶子节点,无需建立救援点,因为无论与之相连的哪个割点坍塌,该节点都能走到叶子节点,而叶子节点已经建立救援点
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;

typedef unsigned long long ULL;
const int N = 1010, M = 1010;
int h[N], e[M], ne[M], idx;
vector<int> dcc[N];
int dcnt, root;
int dfn[N], low[N], tp;
int stk[N], tt;
bool st[N];
int n, m;

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

void tarjan(int x)
{
    low[x] = dfn[x] = ++ tp;
    stk[ ++ tt ] = x;
    if (x == root && h[x] == -1)
    {
        dcnt ++ ;
        dcc[dcnt].push_back(x);
        return;
    }
    int t = 0;
    for (int i = h[x]; i != -1; i = ne[i])
    {
        int y = e[i];
        if (!dfn[y])
        {
            tarjan(y);
            low[x] = min(low[x], low[y]);
            if (low[y] >= dfn[x])
            {
                t ++, dcnt ++ ;
                if (x != root || t > 1) st[x] = true;
                int u;
                do {
                    u = stk[tt -- ];
                    dcc[dcnt].push_back(u);
                } while (u != y);
                dcc[dcnt].push_back(x);
            }
        }
        else low[x] = min(low[x], dfn[y]);
    }
}

int main()
{
    int T = 1;
    while (scanf("%d", &m), m)
    {
        for (int i = 0; i < N; ++ i ) dcc[i].clear();
        memset(h, -1, sizeof(h));
        memset(dfn, 0, sizeof(dfn));
        memset(st, false, sizeof(st));
        tp = dcnt = idx = tt = n = 0;
        for (int i = 0; i < m; ++ i )
        {
            int x, y;
            scanf("%d%d", &x, &y);
            add(x, y), add(y, x);
            n = max(n, x), n = max(n, y);
        }
        for (root = 1; root <= n; ++ root )
            if (!dfn[root]) tarjan(root);
        
        ULL sum = 1; int ans = 0;
        for (int i = 1; i <= dcnt; ++ i )
        {
            int t = 0;
            for (int j = 0; j < dcc[i].size(); ++ j )
                if (st[dcc[i][j]]) 
                    t ++ ;
            if (t == 0) 
            {
                if (dcc[i].size() > 1) ans += 2, sum *= ((ULL)dcc[i].size() * (dcc[i].size() - 1)) / 2;
                else ans ++ ;
            }
            else if (t == 1) ans += 1, sum *= (dcc[i].size() - 1);
        }
        printf("Case %d: %d %llu\n", T ++ , ans, sum);
    }
    return 0;
}

debug:由于多组测试数据,没有初始化干净所有元素
最后统计救援点数量以及方案总数时,没有对孤立点进行特判

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

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

相关文章

(文章复现)基于灰狼算法(GWO)的交直流混合微网经济调度matlab代码

参考文献&#xff1a; [1]高瑜,黄森,陈刘鑫等.基于改进灰狼算法的并网交流微电网经济优化调度[J].科学技术与工程, 2020,20(28):11605-11611. [2]邓长征,冯朕,邱立等.基于混沌灰狼算法的交直流混合微网经济调度[J].电测与仪表, 2020, 57(04):99-107. 这两篇文章不管是从模型、…

【JavaScript】new 的原理以及实现

网道 - new 命令的原理 使用new命令时&#xff0c;它后面的函数依次执行下面的步骤。 创建一个空对象&#xff0c;作为将要返回的对象实例。将这个空对象的原型&#xff0c;指向构造函数的prototype属性。将这个空对象赋值给函数内部的this关键字。如果构造函数返回了一个对象…

[内网渗透]CFS三层靶机渗透

文章目录 [内网渗透]CFS三层靶机渗透网络拓扑图靶机搭建Target10x01.nmap主机探活0x02.端口扫描0x03.ThinkPHP5 RCE漏洞拿shell0x04.上传msf后门(reverse_tcp)反向连接拿主机权限 内网渗透Target2&#xff08;1&#xff09;路由信息探测&#xff08;2&#xff09;msf代理配置&a…

Air001基于Keil环境点灯和调试输出工程配置

Air001基于Keil环境点灯和调试输出工程配置 &#x1f4cc;官方环境搭建教程介绍&#xff1a;https://wiki.luatos.com/chips/air001/Air001-MDK.html&#x1f516;本人使用的是基于HAL库环境搭建的。&#x1f4cd;SDK开发资源链接&#xff1a;https://gitee.com/openLuat/luato…

私域流量宝工具源码搭建-含详细使用说明

&#x1f44b;私域流量宝致力于为个人、团队提供基于微信私域流量的推广、引流的效率工具。可减轻人力&#xff0c;有效降低资源损失、流量流失的几率。引流宝完全开源&#xff0c;免费&#xff0c;可商用、可任意二次开发。引流宝可以辅助你更好地开展营销活动推广&#xff01…

【Docker系列】push镜像报错问题解决方案

1 问题描述 docker push 报这个错&#xff0c;unknown blob 详细报错内容&#xff1a; Use docker scan to run Snyk tests against images to find vulnerabilities and learn how to fix them The push refers to repository [192.******/*******/*************] 3b3341e9d03…

2498. 青蛙过河 II;2568. 最小无法得到的或值;1954. 收集足够苹果的最小花园周长

2498. 青蛙过河 II 核心思想&#xff1a;这题有点开脑洞&#xff0c;就是如果想让代价最小只能是隔一个石头跳&#xff0c;因为其他方法的路径都会形成比这种方法大的结果&#xff0c;然后我们只需要统计出间隔石头的最大值即可。 2568. 最小无法得到的或值 核心思想&#xf…

【第二阶段】kotlin语言的匿名函数类型推断

1.常规匿名函数写法&#xff1a; 如果使用了":",必须给定参数类型和 返回值类型如下&#xff1a; val meThod:()->Int{}2.匿名函数“”&#xff0c;返回类型推断 使用类型推断“”&#xff0c;根据返回值的类型推断 fun main() {/** 常规匿名函数写法&#xff1a…

2023年最新最全软件测试面试题大全

一、面试基础题 简述测试流程: 1、阅读相关技术文档&#xff08;如产品PRD、UI设计、产品流程图等&#xff09;。 2、参加需求评审会议。 3、根据最终确定的需求文档编写测试计划。 4、编写测试用例&#xff08;等价类划分法、边界值分析法等&#xff09;。 5、用例评审(…

哈希unordered系列介绍(上)

一.Unordered_map,Unordered_set介绍 在之前我们已经介绍过set,map,multiset等等关联式容器&#xff0c;它们的底层是红黑树进行模拟实现的&#xff0c;在查询时效率可达到 l o g 2 N log_2 N log2​N&#xff0c;即最差情况下需要比较红黑树的高度次&#xff0c;当树中的节点…

【Linux】IP协议——网络层

目录 IP协议 基本概念 IP协议格式 分片与组装 网段划分 特殊的IP地址 IP地址的数量限制 私网IP地址和公网IP地址 路由 路由表生成算法 IP协议 IP协议全称为“网际互连协议&#xff08;Internet Protocol&#xff09;”&#xff0c;IP协议是TCP/IP体系中的网络层协议…

【LangChain】Memory

概要 大多数LLM应用都有对话界面。对话的一个重要组成部分是能够引用对话中先前介绍的信息。至少&#xff0c;对话系统应该能够直接访问过去消息的某些窗口。更复杂的系统需要有一个不断更新的世界模型&#xff0c;这使得它能够执行诸如维护有关实体及其关系的信息之类的事情。…

【D3S】REST接口文档自动生成 - 集成smart-doc并同步配置到Torna

目录 一、引言二、maven插件三、smart-doc.json配置四、smart-doc-maven-plugin相关命令五、推送文档到Torna六、通过Maven Profile简化构建 一、引言 D3S&#xff08;DDD with SpringBoot&#xff09;为本作者使用DDD过程中开发的框架&#xff0c;目前已可公开查看源码&#…

Object.assign详解

一、Object.assign是什么&#xff1f; Object.assign( )方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。 二、用法 Object.assign(target, ...sources) 参数&#xff1a;target ——>目标对象 source ——>源对象 返回值&#xff1a;…

使用生成式 AI 模仿人类行为

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可编辑的3D应用场景 这项研究被 2023 年学习表征国际会议 &#xff08;ICLR&#xff09; 接受&#xff0c;该会议致力于推进通常称为深度学习的人工智能分支。 图 1&#xff1a;我们的方法概述。 扩散模型已成为一类强大的生…

【JVM】JVM垃圾收集器

文章目录 什么是JVM垃圾收集器四种垃圾收集器&#xff08;按类型分&#xff09;1.串行垃圾收集器(效率低&#xff09;2.并行垃圾收集器(JDK8默认使用此垃圾回收器&#xff09;3.CMS&#xff08;并发&#xff09;垃圾收集器(只针对老年代垃圾回收的&#xff09; 什么是JVM垃圾收…

SDR硬件方案

以射频硬件为线索&#xff0c;梳理常见SDR&#xff08;软件无线电&#xff09;方案。SDR硬件位于天线和数字信号处理之间&#xff0c;负责把无线电信号数字化&#xff0c;交由主机或者嵌入式系统&#xff08;FPGA、DSP&#xff0c;MCU&#xff09;处理。SDR硬件一般包含射频和数…

Python Opencv实践 - 图像缩放

import cv2 as cv import numpy as np import matplotlib.pyplot as pltimg_cat cv.imread("../SampleImages/cat.jpg", cv.IMREAD_COLOR) plt.imshow(img_cat[:,:,::-1])#图像绝对尺寸缩放 #cv.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]) #指定Size大…

18.3.0:Dynamic Web TWAIN Crack Web 文档扫描 SDK

Dynamic Web TWAIN用于快速部署 Web 应用程序的文档扫描 SDK&#xff0c;文档扫描SDK&#xff0c;&#xff0c;超过 5300 家公司信任 Dynamic Web TWAIN &#xff0c;因其稳健性和安全性而受到超过 5300 家公司的信赖&#xff0c;Dynamic Web TWAIN 是一款基于浏览器的文档扫描…

微信开发之一键获取标签好友的技术实现

简要描述&#xff1a; 获取标签列表 请求URL&#xff1a; http://域名地址/getContactLabelList 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/jsonAuthorization&#xff1a;login接口返回 参数&#xff1a; 参数名必选…