题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(n\) 个节点 \(m\) 条边的无向连通图,定义城市 \(u\) 的建设难度为 \(\max_{1 \leq i \leq n} d(u, i)\)(即 \(u\) 到其他所有节点的最远距离,也称为图的偏心距)。求建设难度最小的城市(图的中心),若有多个取编号最小。这是一个多源BFS求图中心问题。

  2. 算法选择

    • BFS(广度优先搜索):无权图单源最短路的标准算法
    • 全源BFS:对每个节点运行一次BFS,计算其到所有其他节点的最远距离
    • 最小偏心距筛选:记录所有节点中最小的最大距离(偏心距),取对应节点
  3. 关键步骤

    • 读入数据\(n, m\)\(m\) 条边,建立邻接表
    • 对每个节点 \(i\) 运行BFS
      • 初始化 vis 数组,maxs = 0
      • 队列存储 (节点, 距离)
      • 标准BFS扩展,更新 maxs = max(maxs, d)
      • BFS结束后,若 maxs < ansd,更新 ansd = maxs, ansid = i
    • 输出 ansid
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \cdot (n + m))\),对每个节点运行一次BFS
    • 空间复杂度:\(O(n + m)\),邻接表和访问标记
  5. 全源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
posted @ 2026-08-10 12:27  团爸讲算法  阅读(19)  评论(0)    收藏  举报