洛谷题单指南-图论之树-P5536 【XR-3】核心城市

原题链接:https://www.luogu.com.cn/problem/P5536

题意解读:核心城市互相连通,确定k个核心城市,使得到核心城市距离最大值最小。

解题思路:

1、k个城市不好确定,可以先考虑1个

要求一个点,使得到其他点最大值最小,这是树的中心概念。

树的中心可以借助换根DP来求解:

第一次dfs:求出每个节点所在子树能到达的最远距离d1和次远距离d2,并记录路径

第二次dfs:求出每个节点不往子树走而是往上走能到的最远距离up

枚举每一个点,根据max(d1[i], up[i])求得一个min值,这时的i即是中心。

2、要选择k个核心城市,中心必选

证明:如果不选中心,中心到非核心点的距离是最大值中最小的,必有一条路径超过中心能到的最远距离,不是最佳答案。

3、第一核心城市选中心,第二城市选择与核心城市邻接的,且能达到距离最远的点

证明:如果不选能到达距离最远的点,必有一条经过该点路径超过到核心城市的最远距离,不是最佳答案。

4、从中心开始,如上选择k个点,剩下的核心点的邻接点中,能到距离最远的点就是核心城市能到的最远距离经过的点

用优先队列可以实现从中心点不断扩展选择k个能到达距离最远的点,第k+1个点能到达的最远距离再加1就是答案。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 100005, INF = 0x3f3f3f3f;

vector<int> g[N];
int d1[N], d2[N], up[N]; //节点往子树能到的最大深度d1、次大深度d2、往父节点方向能到的最大深度up
int to1[N], to2[N]; //在最大深度路径上的下一个节点to1、次大深度路径上的下一个节点to2
priority_queue<pair<int, int>> q; //{节点能到的最远距离, 节点编号}
bool vis[N];
int n, k;

void dfs1(int u, int p)
{
    for (auto v : g[u]) 
    {
        if (v == p) continue; // 跳过父节点
        dfs1(v, u); // 先递归处理子节点
        int d = d1[v] + 1; // 当前子树的深度
        if (d >= d1[u]) // 如果比最大深度大
        { 
            d2[u] = d1[u]; // 原来的最大变为次大
            to2[u] = to1[u]; // 对应的节点也要更新
            d1[u] = d; // 更新最大深度
            to1[u] = v; // 更新最大深度路径上的下一个节点
        } 
        else if (d > d2[u]) // 如果比次大深度大但不超过最大深度
        { 
            d2[u] = d; // 更新次大深度
            to2[u] = v; // 更新次大深度路径上的下一个节点
        }
    }
}

void dfs2(int u, int p)
{
    for (auto v : g[u])
    {
        if (v == p) continue; // 跳过父节点
        // 计算子节点v的up值
        if (to1[u] == v) // 如果v是u的最大深度路径上的下一个节点
        {
            // 则v向上走的最大深度是max(u的次大深度+1, u的up+1)
            up[v] = max(d2[u] + 1, up[u] + 1);
        }
        else // 如果v不是u的最大深度路径上的下一个节点
        {
            // 则v向上走的最大深度是max(u的最大深度+1, u的up+1)
            up[v] = max(d1[u] + 1, up[u] + 1);
        }
        dfs2(v, u); // 递归处理子节点
    }
}

int solve(int dist, int center)
{
    //从中心开始,依次选取能到达距离最远的领接点,直到k个
    //队列里剩下的第一个就是距离核心城市最远的路径上的点
    int cnt = 0;
    q.push({dist, center});
    while(k--)
    {
        pair<int, int> p = q.top(); q.pop();
        int u = p.second;
        vis[u] = true;
        for(auto v : g[u])
        {
            if(vis[v]) continue;
            q.push({d1[v], v});
        }
    }
    return q.top().first + 1;
}

int main()
{

    cin >> n >> k;
    for(int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    dfs1(1, 0);
    dfs2(1, 0);
    int center = 0; //树的中心
    int dist = INF; //中心能到的最远距离
    for(int i = 1; i <= n; i++)
    {
        int t = max(d1[i], up[i]);
        if(t < dist) dist = t, center = i; //找到树的中心
    }
    memset(d1, 0, sizeof(d1)); memset(d2, 0, sizeof(d2));
    memset(to1, 0, sizeof(to1)); memset(to2, 0, sizeof(to2));
    dfs1(center, 0); // 以中心节点为根重新计算每个节点的最大深度
    cout << solve(dist, center) << endl;
    
    return 0;
}

 

posted @ 2025-03-17 15:50  hackerchef  阅读(46)  评论(0)    收藏  举报