题解:洛谷 P5536 核心城市

【题目来源】

洛谷:P5536 【XR-3】核心城市 - 洛谷

【题目描述】

X 国有 \(n\) 座城市,\(n - 1\) 条长度为 \(1\) 的道路,每条道路连接两座城市,且任意两座城市都能通过若干条道路相互到达,显然,城市和道路形成了一棵树。

X 国国王决定将 \(k\) 座城市钦定为 X 国的核心城市,其余城市为非核心城市。这 \(k\) 座核心城市需满足以下两个条件:

  1. \(k\) 座城市可以通过道路,在不经过非核心城市的情况下两两相互到达。
  2. 定义某个非核心城市与这 \(k\) 座核心城市的距离为,这座城市与 \(k\) 座核心城市的距离的最小值。

为了衡量交通状况,国王发明了交通拥堵度,它为所有非核心城市与核心城市的距离中的最大值。

问题来了,如何安排核心城市才能使交通拥堵度最小呢?请输出满足条件的最小交通拥堵度。

【输入】

第一行 \(2\) 个正整数 \(n,k\)

接下来 \(n - 1\) 行,每行 \(2\) 个正整数 \(u,v\),表示第 \(u\) 座城市与第 \(v\) 座城市之间有一条长度为 \(1\) 的道路。

【输出】

一行一个整数,表示满足条件的最小交通拥堵度。

【输入样例】

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

【输出样例】

1

【核心思想】

  1. 问题分析:给定一棵 \(n\) 个节点的树,选择 \(k\) 个核心城市(需连通),最小化非核心城市到核心城市集的最小距离的最大值(交通拥堵度)。这是一个树的直径 + 贪心覆盖问题,关键在于:最优核心城市集是树的直径上的某段连续路径,通过三次DFS找到直径和中心,然后贪心选取覆盖范围最大的节点。

  2. 算法选择

    • 三次DFS求直径:两次DFS找直径端点,一次DFS记录直径路径
    • 树的中心定位:直径中点作为根,具有最小的最大子树深度
    • 子树高度计算:以中心为根DFS,计算每个节点的子树高度 ans[u]
    • 贪心选取:将 ans 降序排序,选前 \(k\) 个最大的作为核心城市,第 \(k+1\) 大的即为最小交通拥堵度
  3. 关键步骤

    • 第一次DFS:从任意点(如1)出发,找到最远点 st(直径一端)
    • 第二次DFS:从 st 出发,找到最远点 ed(直径另一端),同时得到直径长度 maxx
    • 记录直径路径dfs_2sted 记录路径到 router[]
    • 确定中心root = router[maxx / 2](直径中点)
    • 第三次DFS:以 root 为根,计算每个节点的子树最大深度 max_deep[u] 和子树高度 ans[u] = max_deep[u] - deep[u] + 1
    • 排序输出ans 降序排序,输出 ans[k+1]
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \log n)\),三次DFS \(O(n)\),排序 \(O(n \log n)\)
    • 空间复杂度:\(O(n)\),邻接表和多个辅助数组
  5. 树直径 + 贪心覆盖的核心思想

    • 核心城市的连通性约束\(k\) 个核心城市必须连通,最优策略是选择直径上的连续段,因为直径是树中最长路径,覆盖直径即可有效控制全局
    • 中心根的最优性:以直径中点为根,各子树深度相对均衡,最小化最大子树高度
    • 子树高度的意义ans[u] 表示以 \(u\) 为核心城市时,能覆盖的子树范围(高度)。选 ans 最大的 \(k\) 个节点,相当于选覆盖能力最强的 \(k\) 个核心
    • \(k+1\) 大的含义:前 \(k\) 个最大的 ans 被选为核心,它们覆盖范围内的非核心城市距离 \(\leq\) 对应 ans。第 \(k+1\) 大的 ans 是未被选中的城市中覆盖范围最大的,即最小化的最大距离
    • 适用于"树上选 \(k\) 个连通点最小化最大距离"问题,核心在于直径性质和贪心覆盖

【算法标签】

普及+ #树形DP

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 100005, M = 2 * N;
int n, k, st, ed, t, maxx, root;  // n: 节点数,k: 选择的节点数,st: 直径一端,ed: 直径另一端,t: 临时节点,maxx: 最大深度,root: 树的中心
int h[N], e[M], ne[M], idx;  // 邻接表存储树
int deep[N], max_deep[N];  // deep: 节点深度,max_deep: 子树最大深度
int router[N], tmp[N], ans[N];  // router: 直径路径,tmp: 临时路径,ans: 每个节点为根的子树最大深度

// 添加无向边
void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

// 第一次DFS,求树的直径的一端
void dfs_1(int u, int fa)
{
    deep[u] = deep[fa] + 1;  // 计算节点深度
    if (deep[u] > maxx)  // 更新最大深度
    {
        maxx = deep[u];
        t = u;  // 记录最深节点
    }
    for (int i = h[u]; i != -1; i = ne[i])  // 遍历所有邻接点
    {
        int v = e[i];
        if (v == fa)  // 跳过父节点
            continue;
        dfs_1(v, u);
    }
}

// 第二次DFS,求直径路径
void dfs_2(int u, int fa, int step)
{
    if (u == ed)  // 如果到达直径的另一端
    {
        memcpy(router, tmp, sizeof(router));  // 复制路径
    }
    for (int i = h[u]; i != -1; i = ne[i])  // 遍历所有邻接点
    {
        int v = e[i];
        if (v == fa)  // 跳过父节点
            continue;
        tmp[step + 1] = v;  // 记录路径
        dfs_2(v, u, step + 1);
    }
}

// 第三次DFS,计算每个节点的最大深度和ans值
void dfs_3(int u, int fa)
{
    deep[u] = deep[fa] + 1;  // 计算节点深度
    max_deep[u] = deep[u];  // 初始化最大深度为当前深度
    for (int i = h[u]; i != -1; i = ne[i])  // 遍历所有邻接点
    {
        int v = e[i];
        if (v == fa)  // 跳过父节点
            continue;
        dfs_3(v, u);
        max_deep[u] = max(max_deep[u], max_deep[v]);  // 更新子树最大深度
    }
    ans[u] = max_deep[u] - deep[u] + 1;  // 计算以u为根的子树高度
}

int main()
{
    cin >> n >> k;  // 输入节点数和选择的节点数
    memset(h, -1, sizeof(h));  // 初始化邻接表

    for (int i = 1; i < n; i++)  // 输入n-1条边
    {
        int u, v;
        cin >> u >> v;
        add(u, v), add(v, u);  // 添加无向边
    }

    t = maxx = 0;
    dfs_1(1, 0);  // 第一次DFS,找到直径一端
    st = t;  // 直径起点

    t = maxx = 0;
    dfs_1(st, 0);  // 第二次DFS,找到直径另一端
    ed = t;  // 直径终点

    tmp[0] = st;  // 初始化路径起点
    dfs_2(st, 0, 0);  // 寻找直径路径
    root = router[maxx / 2];  // 找到树的中心(直径中点)

    memset(deep, 0, sizeof(deep));
    dfs_3(root, 0);  // 第三次DFS,计算每个节点的ans值

    sort(ans + 1, ans + n + 1, greater<int>());  // 将ans从大到小排序
    cout << ans[k + 1] << endl;  // 输出第k+1大的值
    return 0;
}

【运行结果】

6 3
1 2
2 3
2 4
1 5
5 6
1
posted @ 2026-08-11 11:27  团爸讲算法  阅读(14)  评论(0)    收藏  举报