割点和桥

news/2025/12/7 20:02:15/文章来源:https://www.cnblogs.com/zheyutao/p/19316044/cut

前置知识

无向图的连通性,主要研究割点和桥。

基础定义

  • 割点:在无向图中,删去后使得连通分量数增加的点称为 割点。形式化地,对于一个无向连通图 \(G = (V,E)\),存在一个点 \(x \in V\),使删除与 \(x\) 相关联的边后,图分裂成两个或两个以上的不连通的子图,称 \(x\) 即为图 \(G\) 的割点。

  • 桥:在无向图中,删去后使得连通分量数增加的边称为 割边,也称 。形式化地,对于一个无向连通图 \(G = (V,E)\),存在一条边 \(e \in E\),若 \(G - e\) 不连通,则称 \(e\) 即为图 \(G\) 的桥。

根据割点和桥的定义,孤立点 和 孤立边的两个端点 都不是割点,孤立边是桥。

延伸定义

  • 点双连通图:不存在割点的无向图称为 点双连通图。结合上述分析,孤立点和孤立边均为点双连通图。

  • 边双连通图:不存在割边的无向图称为 边双连通图。结合上述分析,孤立点是边双连通图,但孤立边不是边双连通图。

  • 点双连通分量:一张图的极大点双连通子图称为 点双连通分量,简称 点双

  • 边双连通分量:一张图的极大边双连通子图称为 边双连通分量,简称 边双

点双和边双缩点后均得到一棵树,而强连通分量缩点后得到一张有向无环图。

笔者在本文中只讨论如何求解割点与桥,在往后的学习中再作补充。

Tarjan 求割点

不加证明地给出用 割点判定法则,读者画图不难理解:

  1. \(v\) 是非根结点 \(u\) 的子结点,且 \(low[v] \geq dfn[u]\),则 \(u\) 是割点;

  2. 若根节点 \(u\) 在搜索树中至少有 \(2\) 棵子树,则 \(u\) 是割点。

注意:割点判定法则须谨记对于 \(u\) 是否为根结点的讨论。

模版题 代码如下,使用 Tarjan 算法求无向图 \(G\) 的所有割点的时间复杂度为 \(O(n + m)\)

#include <bits/stdc++.h>
using namespace std;
const int N = 2e4 + 8;
int n, m, dfn[N], low[N], tim, ans, root;
bool cut[N]; // 记录该点是否为割点
vector<int> e[N];
void Tarjan(int u) {dfn[u] = low[u] = ++tim;bool flag = false; // u是否为割点int cld = 0; // u的儿子个数for (int v : e[u]) {if (!dfn[v]) {Tarjan(v);low[u] = min(low[u], low[v]);cld++;if (low[v] >= dfn[u]) flag = true; // 符合判定1} else low[u] = min(low[u], dfn[v]);}if (u == root && cld < 2) flag = false; // 不符合判定2if (flag) ans++, cut[u] = true;
}
int main() {cin >> n >> m;for (int i = 1, u, v; i <= m; i++) {cin >> u >> v;e[u].push_back(v); e[v].push_back(u);}for (int i = 1; i <= n; i++) if (!dfn[i]) root = i, Tarjan(i);cout << ans << '\n';for (int i = 1; i <= n; i++) if (cut[i]) cout << i << ' ';return 0;
}

Tarjan 求桥

使用 Tarjan 算法求桥的时间复杂度为 \(O(n + m)\),但需要特别注意是否有重边。

无重边

同样不加证明地给出 桥的判定法则,读者可以画图理解两者的差别:

\(v\) 是点 \(u\) 的子结点,且 \(low[v] > dfn[u]\),则边 \(e = (u, v)\) 是桥。

对比割点判定法则,没有等号的原因为:删去的是边而非结点,所以只要子树内的结点能绕过 \(e\) ,到达包括 \(u\) 在内的子树外结点,那么 \(e\) 就是割边。

代码如下。

void Tarjan(int u, int f) {dfn[u] = low[u] = ++tim;for (int v : e[u]) {if (v == f) continue;if (!dfn[v]) {Tarjan(v, u);low[u] = min(low[u], low[v]);if (low[v] > dfn[u]) ans++; // 割边的数目加1} else low[u] = min(low[u], dfn[v]);}
}

有重边

Tarjan 求桥有个细节,就是判断非树边。对于当前边 \(v \rightarrow u\),若 \(u\)\(v\) 的父亲,则跳过这条边。

但这样做在有重边时会出现错误,原因是算法会将重边也判定为非树边。

解决方法是记录边的编号。对于 vector,在 push_back 时将当前边的编号一并压入。

代码如下。

int n, m, dfn[N], low[N], tim;
bool bri[M]; // 是否为桥
vector<pair<int, int> > e[N]; // pair.first记录结点编号,pair.second记录边编号
void Tarjan(int u, int p) {dfn[u] = low[u] = ++tim;for (auto [v, i] : e[u]) {if (p == i) continue; // p记录上一条边的编号if (!dfn[v]) {Tarjan(v, i);low[u] = min(low[u], low[v]);if (low[v] > dfn[u]) bri[i] = true;} else low[u] = min(low[u], dfn[v]);}
}

例题

Blockade

首先对第 \(i\) 个结点讨论。

如果第 \(i\) 个点不是割点,那么原图只分为孤点 \(i\) 和剩余 \((n - 1)\) 个点所构成的连通块。由于要求有序点对,所以共有 \(2 * (n - 1)\) 对。

如果第 \(i\) 个点不是割点,那么原图分为孤点 \(i\)\(k\) 个连通块,设第 \(j\) 个连通块的大小为 \(sz[j]\),约定“\(a\) 在前”意即在有序数对 \((a,b)\) 中的 \(a\) 位置。对于点 \(i\)\((k-1)\) 棵子树在前,共有 \(\sum \limits_ {j = 1} ^ {k - 1} sz[j] * (n - sz[j])\) 对;对于割点 \(i\) 在前,共有 \((n-1)\) 对;设除割点 \(i\) 及被拆出的子树中的点共有 \(sum\) 个。对于除上述情况外的点,共有 \(sum * (n - sum)\) 对。

最后实现时按照上述分析维护即可,注意要开 long long。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 8;
int n, m, tim, dfn[N], low[N], ans[N], sz[N];
vector<int> e[N];
void Tarjan(int u, int f) {dfn[u] = low[u] = ++tim;sz[u] = 1;int chi = 0, sum = n - 1;bool flag = false;for (int v : e[u]) {if (v == f) continue;if (!dfn[v]) {Tarjan(v, u);chi++;low[u] = min(low[u], low[v]);sz[u] += sz[v];if (low[v] >= dfn[u]) {sum -= sz[v];ans[u] += sz[v] * (n - sz[v]);flag = true;}} else low[u] = min(low[u], dfn[v]);}if (u == 1 && chi < 2) flag = false;if (!flag) ans[u] = 2 * (n - 1);else ans[u] += n - 1 + sum * (n - sum);
}
signed main() {cin >> n >> m;for (int i = 1, u, v; i <= m; i++) {cin >> u >> v;e[u].push_back(v);e[v].push_back(u);}Tarjan(1, 0);for (int i = 1; i <= n; i++) cout << ans[i] << '\n';return 0;
}

分离的路径

题目相当于要求添加最少得边使原图变成一个边双连通图。换句话说,图中所有点的度数至少为 \(2\)。考虑度数为 \(1\) 的点,显然它们应至少连 \(1\) 条边。贪心地,每条边都连接 \(2\) 个度数均为 \(1\) 的点的策略是最优的。如果度数为 \(1\) 的点的个数是奇数,那么仍要多连 \(1\) 条边才能符合题意。

结论:最小路径数为边双缩点得到的缩点树 \(T\) 的叶子结点个数除以 \(2\) 向上取整。

一个比较简单的实现方法是:先 Tarjan 找出所有桥,再 DFS 找出所有边双连通分量,最后统计边双连接的桥的数目为 \(1\) 的个数即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 5e3 + 8, M = 1e4 + 8;
int n, m, dfn[N], low[N], tim, ans, res;
bool bri[M], vis[N];
vector<pair<int, int> > e[N];
void Tarjan(int u, int p) { // 找出所有桥dfn[u] = low[u] = ++tim;for (auto [v, i] : e[u]) {if (p == i) continue;if (!dfn[v]) {Tarjan(v, i);low[u] = min(low[u], low[v]);if (low[v] > dfn[u]) bri[i] = true;} else low[u] = min(low[u], dfn[v]);}
}
void dfs(int u) { // 找出所有边双if (vis[u]) return;vis[u] = true;for (auto [v, i] : e[u]) {if (bri[i]) {res++;continue;}dfs(v);}
}
int main() {cin >> n >> m;for (int i = 1, u, v; i <= m; i++) {cin >> u >> v;e[u].push_back({v, i});e[v].push_back({u, i});}Tarjan(1, 0);for (int i = 1; i <= n; i++)if (!vis[i]) { res = 0;dfs(i);if (res == 1) ans++; // 若边双只有一条桥,则它是缩点树的叶子结点}cout << (ans + 1) / 2;return 0;
}

嗅探器

类比 Tarjan 求割点的过程,若割点 \(u\) 非根 \(a\) 且子树根 \(v\) 的时间戳小于等于终点 \(b\) 的时间戳,则 \(b\)\(v\) 的子树内,所以 \(u\) 点即所求的嗅探器安装位置。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 8;
int n, a, b, dfn[N], low[N], tim, ans = 1e9;
vector<int> e[N];
void Tarjan(int u) {dfn[u] = low[u] = ++tim;bool flag = false;int cld = 0;for (int v : e[u]) {if (!dfn[v]) {Tarjan(v);low[u] = min(low[u], low[v]);cld++;if (low[v] >= dfn[u] && u != a && dfn[b] >= dfn[v]) flag = true;} else low[u] = min(low[u], dfn[v]);}if (flag) ans = min(ans, u);
}
int main() {cin >> n;int u, v;while (cin >> u >> v) {if (u == 0 && v == 0) break;e[u].push_back(v);e[v].push_back(u);}cin >> a >> b;Tarjan(a);if (ans != 1e9) cout << ans;else cout << "No solution";return 0;
}

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

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

相关文章

AI元人文构想全维解构:从意义行为原生到文明价值操作系统

AI元人文构想全维解构:从意义行为原生到文明价值操作系统 引言:范式革命的必然性 传统人工智能伦理中的"价值对齐"范式正陷入根源性困境。这种范式试图将人类价值观编码为AI系统可遵循的静态规则,却在认识…

YII框架的三条经典利用链的探究

利用链一 从BatchQueryResult出发,关键源码:(下文关于类的代码都只保留了关键部分)可以看到reset()方法里面的 $this->_dataReader->close(); 是可控的,并且在调用__destruct()会直接指向reset();方法 不难想…

HELLDIVERS 2 地狱潜兵 2 缩小体积至22.54G 教程

23GB 的游戏 根据地狱潜兵官方技术博客: 通过彻底移除数据重复,我们成功将 PC 端的安装体积从约 154GB 减少到约 23GB,总计节省约 131GB(~85%)! 教程 1.最开始是131.03GB2. “右键”游戏-->“属性”3. “测试…

深度解析人工神经元输入机制

人工神经元输入机制深度解析<script src="https://cdn.tailwindcss.com"></script><script src="https://cdn.jsdelivr.net/npm/chart.js"></script>body { font-family:…

Milvus GUI ATTU Docker 容器化部署指南

ATTU是一款全方位的Milvus管理工具(Milvus GUI),旨在简化Milvus向量数据库的管理流程,降低运维成本。通过直观的图形界面,用户可以轻松完成Milvus集群监控、数据管理、向量检索等核心操作。采用Docker容器化部署A…

如何使用QFontDatabase在Qt应用程序中嵌入字体

您可以使用QFontDatabase将True Type字体或Open Type字体嵌入到Qt应用程序中。您可以链接到外部字体文件或链接到作为资源嵌入的字体。 首先,确保包含对QFontDatabase类的引用: #include <QFontDatabase>;要链…

人工神经元输入机制深度解析:从理论基础到工程实践的全面指南

人工神经元输入机制深度解析:从理论基础到工程实践的全面指南引言:人工神经元输入机制的核心概念与研究背景 1.1 人工神经元的理论起源与数学定义 人工神经元的概念起源于 1943 年 McCulloch 和 Pitts 的开创性工作,…

贪心 [CSP-S 2025] 社团招新

[CSP-S 2025] 社团招新 CSP/NOIP 正在 ACM 化. 前几年 T1 送分往往都是写个模拟即可, 但现在变成考思维题了. 显然我们不妨先不管 \(\dfrac{n}{2}\) 的限制, 一股脑直接去把人扔到对应的社团里, 在从人数最多的社团里把…

P7115 [NOIP2020] 移球游戏 题解

P7115 [NOIP2020] 移球游戏 题解(本蒟蒻的第一篇题解,不喜勿喷) NOIP2020 移球游戏 题解 题目描述 有 \(n + 1\) 根柱子(编号 \(1 \sim n+1\)),前 \(n\) 根柱子上有 \(m\) 个球,第 \(n+1\) 根为空。共有 \(n\) 种…

pdf图片处理

将pdf中的一页另存为图片 在使用pymupdf时,获取到page对象后,使用page.get_pixmap(dpi=500) 这个方法可以将整页保存为图像。但这里有两种方式,一种是使用dpi参数,这个是代表在一英寸里要包含多少个像素,设置越大…

2025年12月本田雅阁更换轮胎推荐:最新性能测评与选购攻略

2025年12月本田雅阁更换轮胎推荐:最新性能测评与选购攻略在城市精英家庭与商务用户的换胎决策图谱中,本田雅阁更换轮胎推荐始终是高热度话题。作为深耕中高端轿车市场多年的经典车型,雅阁在底盘调校、动力匹配与NVH…

获取运行中的exe的窗口标题名

获取运行中的exe的窗口标题名使用spy++

2025年大众帕萨特更换轮胎推荐:玲珑、米其林、马牌哪个是全面优选?

2025年大众帕萨特更换轮胎推荐:玲珑、米其林、马牌哪个是全面优选?在德系B级轿车细分市场中,大众帕萨特凭借严谨的工艺调校与均衡的驾乘质感,长期占据商务与家庭出行的重要位置。伴随用车周期的推进,轮胎更换成为…

12.7

今天没课 没怎么学

安卓页面的布局和生命周期(新手村第三篇) - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

《场景化落地:用 Linux 共享内存解决进程间高效数据传输障碍(终篇)》

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

本地AI模型API网址添加到Open WebUI的方法

如下操作如果需要可以付费找我帮助。设置环境变量。setx HF_ENDPOINT "https://hf-mirror.com"、 pip config set global.index-url https://mirrors.cloud.tencent.com/pypi/simple 根据Open WebUI官方文档…

图像基础核心知识体系

一、 图像基础核心知识体系 1. 像素与分辨率像素:图像的最小单位,一个带有颜色信息的小方点。像素大小通常指图像的总像素数量(宽高),如 19201080(约207万像素)。 分辨率:有两个常见但易混的概念:图像分辨率:…

P14660 你不孤单,我们都在 题解

P14660 你不孤单,我们都在 题解题意 我们有 \(n\) 个朋友,每个朋友有: 当前压力值 \(a_i\) 最大承受值 \(b_i\) 我们可以选择至多一次倾诉活动: 选择任意一些人参加 所有被选中的人的压力值会变成 这些人的压力值的…