当前位置: 首页 > news >正文

CF1000E We Need More Bosses

CF1000E We Need More Bosses

题目描述

题目大意:

给定一个 n n n 个点 m m m 条边的无向图,保证图连通。找到两个点 s , t s,t s,t,使得 s s s t t t必须经过的边最多(一条边无论走哪条路线都经过ta,这条边就是必须经过的边), 2 < = n < = 3 ∗ 1 0 5 , 1 < = m < = 3 ∗ 1 0 5 2<=n<=3*10^5,1<=m<=3*10^5 2<=n<=3105,1<=m<=3105

输入格式

第一行两个整数, n , m n,m n,m

以下m行,每行两个整数 x , y x,y x,y,表示 x y xy xy间有一条无向边相连

输出格式

一个整数,即最多的必须经过边数

感谢@守望 提供翻译

输入输出样例 #1

输入 #1

5 5
1 2
2 3
3 1
4 1
5 2

输出 #1

2

输入输出样例 #2

输入 #2

4 3
1 2
4 3
3 2

输出 #2

3

思路:

求下边的双连通性,然后找一条必经的最长路(题目中写的 s s s t t t 必须经过的边最多),也就是求所有桥的数量。
最长路就是缩点后树的直径,所有的边都是桥,求一下桥的数量就是最多的必须经过边数。

T a r j a n Tarjan Tarjan 算法求 E D C C EDCC EDCC,套模板

inline void add(int a, int b) {e.push_back({b, h[a]});h[a] = e.size() - 1;
}void Tarjan(int u, int in_edge) {low[u] = dfn[u] = ++tot;stk.push(u);for(int i = h[u]; ~i; i = e[i].ne) {int v = e[i].v;if(!dfn[v]) {Tarjan(v, i);low[u] = std::min(low[u], low[v]);if(low[v] > dfn[u]) bri[i] = bri[i ^ 1] = true; // 下个点的最小时间戳比当前点的时间戳大, 说明是桥}else if(i != (in_edge ^ 1)) low[u] = std::min(low[u], dfn[v]); // 这条边不能是反向边}if(low[u] == dfn[u]) {int v;cnt++;do{v = stk.top();stk.pop();dcc[v] = cnt;}while(v != u);}
}

建立新的树:

    tree.resize(cnt + 1);Rep(u, 1, n + 1) {for(int i = h[u]; ~i; i = e[i].ne) {int v = e[i].v;if(bri[i]) { // 这条边是桥int nu = dcc[u], nv = dcc[v];if(nu == nv) continue; // 不能连接相同编号tree[nu].push_back(nv);tree[nv].push_back(nu);}}}

树的直径的求法:两段BFS,一段取任意一个点找到一个距离他最远的那个点 u u u ,然后在从 u u u 开始找,找到距离 u u u 最远的点 v v v ,这两个点之间的路径就是树的直径,也就是最长的距离 s − > t s -> t s>t

inline int BFS(int root) {dist.assign(cnt + 1, INF);q = std::queue<int>();int max_num = root, max_dist = 0;q.push(root);dist[root] = 0;while(!q.empty()) {int u = q.front();q.pop();for(const int& v : tree[u]) {if(dist[v] == INF) { // 没走过dist[v] = dist[u] + 1;if(dist[v] > max_dist) {max_dist = dist[v];max_num = v;}q.push(v);}}}return max_num;
}main:// 计算树的直径int u = BFS(1); // 这个点是任意的int v = BFS(u);

然后找一下从 u u u v v v 一共经过了多少边就是多少桥。

inline void BFS(int u, int v) { // 重载dist.assign(cnt + 1, INF);q = std::queue<int>();q.push(u);dist[u] = 0;while(!q.empty()) {int now = q.front();q.pop();for(const int& ne : tree[now]) {if(dist[ne] == INF) {dist[ne] = dist[now] + 1;q.push(ne);}}}std::cout << dist[v] << '\n';
}

T a r j a n + 树的直径 Tarjan + 树的直径 Tarjan+树的直径 A C c o d e : AC code: ACcode:

#include <iostream>
#include <climits>
#include <limits>
#include <vector>
#include <queue>
#include <stack>typedef unsigned long long ull;
typedef long long ll;
typedef long double ld;
typedef std::pair<int, int> PII;#define rep(i, n) for(int i = 0; i < n; i++)
#define Rep(i, len, n) for(int i = len; i < n; i++)
#define MAX_INT 0x7fffffff
#define MIN_INT 0x80000000const int INF = std::numeric_limits<int>::max();struct edge {int v, ne;
};int n, m, tot = 0, cnt = 0;
std::vector<edge> e;
std::vector<std::vector<int>> tree;
std::vector<int> low, dfn, bri, h, dcc, dist;
std::stack<int> stk;
std::queue<int> q;inline void add(int a, int b) {e.push_back({b, h[a]});h[a] = e.size() - 1;
}void Tarjan(int u, int in_edge) {low[u] = dfn[u] = ++tot;stk.push(u);for(int i = h[u]; ~i; i = e[i].ne) {int v = e[i].v;if(!dfn[v]) {Tarjan(v, i);low[u] = std::min(low[u], low[v]);if(low[v] > dfn[u]) bri[i] = bri[i ^ 1] = true;}else if(i != (in_edge ^ 1)) low[u] = std::min(low[u], dfn[v]);}if(low[u] == dfn[u]) {int v;cnt++;do{v = stk.top();stk.pop();dcc[v] = cnt;}while(v != u);}
}inline int BFS(int root) {dist.assign(cnt + 1, INF);q = std::queue<int>();int max_num = root, max_dist = 0;q.push(root);dist[root] = 0;while(!q.empty()) {int u = q.front();q.pop();for(const int& v : tree[u]) {if(dist[v] == INF) { // 没走过dist[v] = dist[u] + 1;if(dist[v] > max_dist) {max_dist = dist[v];max_num = v;}q.push(v);}}}return max_num;
}inline void BFS(int u, int v) { // 重载dist.assign(cnt + 1, INF);q = std::queue<int>();q.push(u);dist[u] = 0;while(!q.empty()) {int now = q.front();q.pop();for(const int& ne : tree[now]) {if(dist[ne] == INF) {dist[ne] = dist[now] + 1;q.push(ne);}}}std::cout << dist[v] << '\n';
}int main(void) {std::ios::sync_with_stdio(false);std::cin.tie(nullptr), std::cout.tie(nullptr);std::cin >> n >> m;low.resize(n + 1);dfn.resize(n + 1);dcc.resize(n + 1);h.resize(n + 1, -1);bri.resize(2 * m + 1, false);rep(i, m) {int x, y;std::cin >> x >> y;add(x, y);add(y, x);}Rep(i, 1, n + 1) if(!dfn[i]) Tarjan(i, -1);if(cnt <= 1) {std::cout << 0 << '\n';return 0;}tree.resize(cnt + 1);Rep(u, 1, n + 1) {for(int i = h[u]; ~i; i = e[i].ne) {int v = e[i].v;if(bri[i]) {int nu = dcc[u], nv = dcc[v];if(nu == nv) continue;tree[nu].push_back(nv);tree[nv].push_back(nu);}}}// 计算树的直径int u = BFS(1);int v = BFS(u);BFS(u, v);return 0;
}

一些问题:
Q:为什么 u 到 v 是必经过的最长路径(树的直径)?
A:
代码中通过两次 BFS 来求树的直径。首先从任意一个点(这里是 1)开始进行 BFS,找到离这个点最远的点 u。然后从 u 点再次进行 BFS,找到离 u 最远的点 v。

根据树的直径的性质,对于一棵树,从任意一个点出发找到的最远点一定是直径的一个端点。所以第一次 BFS 找到的 u 是直径的一个端点。然后从 u 出发再次进行 BFS 找到的 v 就是直径的另一个端点。

这样 u 到 v 的路径就是这棵树的直径,因为树的直径定义为树中任意两点间距离的最大值,而通过上述两次 BFS 的方法保证了找到的 u 和 v 之间的路径是树中最长的路径。

Q:在原图中的意义(边双连通分量的角度)?
A:在原图中,边双连通分量内部是没有桥的,而桥连接了不同的边双连通分量。通过求缩点后树的直径,实际上是找到了原图中通过桥连接的两个最远的边双连通分量。u 到 v 的路径上经过的桥是原图中连接不同边双连通分量的关键路径。
Q:树的直径(u 到 v 的路径)在原图中的意义是:
A:最长的桥路径:直径路径上的边都是原图中的桥,这条路径是原图中通过桥连接的最长路径。
必经的关键路径:在原图中,如果要从一个边双连通分量到达另一个边双连通分量,必须通过它们之间的桥。直径路径代表了原图中最远的两个边双连通分量之间的必经之路。
Q:为什么这就是问题的解?
A:直径路径上的桥是原图中连接不同边双连通分量的关键路径。
任何跨越多个边双连通分量的路径都必须经过这些桥。因此,树的直径长度就代表了原图中两个最远的边双连通分量之间的最短路径长度(因为每次跨越一个桥算一步)。

http://www.xdnf.cn/news/272611.html

相关文章:

  • 【转载】【翻译】图解智能体到智能体 (A2A) 协议
  • 冯诺依曼结构与哈佛架构深度解析
  • 【Linux系统】第二节—基础指令(2)
  • 13:图像处理—畸变矫正详解
  • 修复笔记:获取 torch._dynamo 的详细日志信息
  • 【数据结构】励志大厂版·初阶(复习+刷题)排序
  • 【程序+论文】大规模新能源并网下的火电机组深度调峰经济调度
  • TFQMR和BiCGStab方法比较
  • 缓存与数据库的高效读写流程解析
  • 8.1 Python+Docker+企业微信集成实战:自动化报告生成与CI/CD部署全攻略
  • php study 网站出现404 - Page Not Found 未找到
  • 去打印店怎么打印手机文件,网上打印平台怎么打印
  • C++负载均衡远程调用学习之Agent代理模块基础构建
  • 组合模式(Composite Pattern)
  • 探索正态分布:交互式实验带你体验统计之美
  • AI 编程日报 · 2025 年 5 月 04 日|GitHub Copilot Agent 模式发布,Ultralytics 优化训练效率
  • 【Linux】深入理解程序地址空间
  • C语言实现数据结构:堆排序和二叉树_链式
  • JavaScript性能优化实战(9):图像与媒体资源优化
  • 2025-04-26-利用奇异值重构矩阵-美团
  • ActiveMQ 与其他 MQ 的对比分析:Kafka/RocketMQ 的选型参考(一)
  • Git从入门到精通-第四章-更新仓库
  • 2025 年如何使用 Pycharm、Vscode 进行树莓派 Respberry Pi Pico 编程开发详细教程(更新中)
  • C++调试(叁):编译qBreakpad并使用其生成Dump文件
  • 【时间之外】官网视频风波
  • Dagster中的Ops与Assets:数据管道构建的两种选择
  • 主自开发光枪鼠标模拟器实战,使用micro pro板子方式
  • P1537 数字反转(升级版)详解
  • 【C++语法】类和对象(3)
  • 蟋蟀的叫声,大自然的温度计