题解:洛谷 P14921 城市规划
【题目来源】
洛谷:P14921 [GESP202512 七级] 城市规划 - 洛谷
【题目描述】
A 国有 \(n\) 座城市,城市之间由 \(m\) 条双向道路连接,任意一座城市均可经过若干条双向道路到达另一座城市。城市依次以 \(1,2,\ldots,n\) 编号。第 \(i\)(\(1\le i\le m\))条双向道路连接城市 \(u_i\) 与城市 \(v_i\)。
对于城市 \(u\) 和城市 \(v\) 而言,它们之间的连通度 \(d(u,v)\) 定义为从城市 \(u\) 出发到达城市 \(v\) 所需经过的双向道路的最少条数。由于道路是双向的,可以知道连通度满足 \(d(u,v)=d(v,u)\),特殊地有 \(d(u,u)=0\)。
现在 A 国正在规划城市建设方案。城市 \(u\) 的建设难度为它到其它城市的最大连通度。请你求出建设难度最小的城市,如果有多个满足条件的城市,则选取其中编号最小的城市。形式化地,你需要求出使得 \(\max\limits_{1\le i\le n}d(u,i)\) 最小的 \(u\),若存在多个可能的 \(u\) 则选取其中最小的。
【输入】
第一行,两个正整数 \(n,m\),表示 A 国的城市数量与双向道路数量。
接下来 \(m\) 行,每行两个整数 \(u_i,v_i\),表示一条连接城市 \(u_i\) 与城市 \(v_i\) 的双向道路。
【输出】
输出一行,一个整数,表示建设难度最小的城市编号。如果有多个满足条件的城市,则选取其中编号最小的城市。
【输入样例】
3 3
1 2
1 3
2 3
【输出样例】
1
【核心思想】
-
问题分析:给定 \(n\) 个节点 \(m\) 条边的无向连通图,定义城市 \(u\) 的建设难度为 \(\max_{1 \leq i \leq n} d(u, i)\)(即 \(u\) 到其他所有节点的最远距离,也称为图的偏心距)。求建设难度最小的城市(图的中心),若有多个取编号最小。这是一个多源BFS求图中心问题。
-
算法选择:
- BFS(广度优先搜索):无权图单源最短路的标准算法
- 全源BFS:对每个节点运行一次BFS,计算其到所有其他节点的最远距离
- 最小偏心距筛选:记录所有节点中最小的最大距离(偏心距),取对应节点
-
关键步骤:
- 读入数据:\(n, m\) 和 \(m\) 条边,建立邻接表
- 对每个节点 \(i\) 运行BFS:
- 初始化
vis数组,maxs = 0 - 队列存储
(节点, 距离) - 标准BFS扩展,更新
maxs = max(maxs, d) - BFS结束后,若
maxs < ansd,更新ansd = maxs, ansid = i
- 初始化
- 输出
ansid
-
时间/空间复杂度:
- 时间复杂度:\(O(n \cdot (n + m))\),对每个节点运行一次BFS
- 空间复杂度:\(O(n + m)\),邻接表和访问标记
-
全源BFS求图中心的核心思想:
- 偏心距定义:节点 \(u\) 的偏心距 \(ecc(u) = \max_v d(u,v)\),即 \(u\) 到最远节点的距离。图中心是偏心距最小的节点
- 无权图BFS最优性:无权图中BFS按层扩展,第一次访问节点时距离即为最短距离
- 全源遍历的必要性:图中心不一定在直径上(与树不同),必须计算每个节点的偏心距才能确定全局最小
- 编号最小处理:遍历顺序从 \(1\) 到 \(n\),仅在
ansd > maxs(严格大于)时更新,保证相同偏心距时保留编号更小的节点 - 适用于"无权图中心/偏心距"问题,核心在于全源BFS和最小最大距离筛选
【算法标签】
普及 #BFS-图
【代码详解】
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII; // 定义pair类型别名,用于存储(节点编号, 距离)
const int N = 2005; // 定义最大节点数
int n, m, u, v, ansd = 1e9, ansid; // n: 节点数,m: 边数,u、v: 临时变量,ansd: 最小直径,ansid: 对应节点编号
vector<int> a[N]; // 邻接表存储图
queue<PII> q; // BFS队列,存储(节点编号, 距离)
bool vis[N]; // 标记数组,记录节点是否被访问过
// 广度优先搜索函数,计算以x为起点的最大距离
void bfs(int x)
{
memset(vis, 0, sizeof(vis)); // 初始化访问标记数组
int maxs = 0, d; // maxs: 从x出发的最大距离,d: 当前节点距离
q.push({x, 0}); // 起点入队,距离为0
while (!q.empty()) // 队列不为空时循环
{
int u = q.front().first, d = q.front().second; // 获取队首节点和距离
q.pop(); // 弹出队首
if (vis[u]) // 如果节点已访问,跳过
continue;
vis[u] = 1; // 标记节点已访问
maxs = max(maxs, d); // 更新最大距离
for (int i = 0; i < a[u].size(); i++) // 遍历当前节点的所有邻接点
{
q.push({a[u][i], d + 1}); // 邻接点入队,距离加1
}
}
// 更新全局最小直径和对应节点
if (ansd > maxs)
{
ansd = maxs; // 更新最小直径
ansid = x; // 记录达到最小直径的节点编号
}
}
int main()
{
cin >> n >> m; // 输入节点数和边数
while (m--) // 读入所有边
{
cin >> u >> v; // 输入边的两个端点
a[u].push_back(v); // 添加无向边
a[v].push_back(u); // 添加无向边
}
for (int i = 1; i <= n; i++) // 对每个节点进行BFS
bfs(i);
cout << ansid << endl; // 输出中心节点编号
return 0;
}
【运行结果】
3 3
1 2
1 3
2 3
1
浙公网安备 33010602011771号