[XR-3] 核心城市
拓扑剥叶给每个节点分层,选层号最大的 k 个连通节点作核心,答案即第 k+1 大的层号。
OJ: luogu
题目 ID: P5536
难度:普及+/提高-
标签:树拓扑排序贪心
日期: 2026-07-17 02:00
形式化题目
给定一棵
思路
先看一个可以直接验证想法的朴素解:
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-08-13 08:02
* update_at: 2026-08-13 08:02
*/
// brute.cpp:小数据暴力解,使用 01 序列递归枚举每个城市是否为核心城市。
// choose[i] = 1 表示第 i 个城市是核心城市;枚举完整序列后检查连通性并统计答案。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 15;
int n, k;
vector<int> g[MAXN]; // 邻接表存树
int choose[MAXN]; // choose[i]:第 i 个城市是否被选为核心城市
int vis[MAXN]; // 连通性检查与 BFS 共用标记
int best = 1e9;
// 检查当前核心集合是否连通(只经过核心城市能否互相到达)。
bool check_connected() {
int start = -1;
for (int i = 1; i <= n; i++) {
if (choose[i]) {
start = i;
break;
}
}
if (start == -1)
return false;
for (int i = 1; i <= n; i++)
vis[i] = 0;
queue<int> q;
vis[start] = 1;
q.push(start);
int cnt = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
cnt++;
for (int i = 0; i < (int)g[u].size(); i++) {
int v = g[u][i];
if (choose[v] && !vis[v]) {
vis[v] = 1;
q.push(v);
}
}
}
return cnt == k;
}
// 计算非核心城市 start 到最近核心城市的距离。
int dist_to_core(int start) {
for (int i = 1; i <= n; i++)
vis[i] = 0;
queue<pair<int, int> > q; // 队列元素:(节点, 距离)
vis[start] = 1;
q.push(make_pair(start, 0));
while (!q.empty()) {
int u = q.front().first;
int d = q.front().second;
q.pop();
if (choose[u])
return d;
for (int i = 0; i < (int)g[u].size(); i++) {
int v = g[u][i];
if (!vis[v]) {
vis[v] = 1;
q.push(make_pair(v, d + 1));
}
}
}
return -1; // 不会到达
}
// 生成完整 01 序列后在叶子节点统一检查、统计。
void dfs(int dep, int cnt) {
if (dep == n + 1) {
if (cnt != k || !check_connected())
return;
int cur = 0;
for (int i = 1; i <= n; i++) {
if (!choose[i])
cur = max(cur, dist_to_core(i));
}
if (cur < best)
best = cur;
return;
}
// 这一层决定第 dep 个城市选不选。
choose[dep] = 0;
dfs(dep + 1, cnt);
choose[dep] = 1;
dfs(dep + 1, cnt + 1);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
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);
}
dfs(1, 0);
cout << best << '\n';
return 0;
}brute.cpp 用 01 序列枚举每个城市是否进入核心,到叶子节点再统一检查核心个数、连通性并 BFS 计算最远距离。
关键观察是剥叶分层:把树理解成由外到内的同心层——叶子是第一层,剥掉叶子后新叶子是第二层,依此类推。每一轮剥掉的叶子构成一层,向内走一步就进入内层:
text
第一轮剥掉:所有叶子(层号 1)
第二轮剥掉:新产生的叶子(层号 2)
……
第 t 轮剥掉:层号 t剥叶过程保证"层号不小于某个值的节点"始终连通。于是最优核心就是层号最大的
以样例为例,剥叶分层如下:
| 节点 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| 层号 | 2 | 3 | 1 | 1 | 2 | 1 |
代码
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-08-13 08:02
* update_at: 2026-08-13 08:02
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int n, k;
vector<int> g[MAXN]; // 邻接表存树
int degree[MAXN]; // 当前剩余度数(剥叶过程中动态更新)
int layer[MAXN]; // layer[i]:节点 i 在第几轮被剥掉(叶子为第 1 轮)
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
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);
degree[u]++;
degree[v]++;
}
// 拓扑剥叶:每一轮把所有当前叶子(剩余度数 <= 1)标记层数并删除。
queue<int> q;
for (int i = 1; i <= n; i++) {
if (degree[i] <= 1) {
layer[i] = 1;
q.push(i);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = 0; i < (int)g[u].size(); i++) {
int v = g[u][i];
if (degree[v] > 1) {
degree[v]--;
if (degree[v] == 1) {
layer[v] = layer[u] + 1;
q.push(v);
}
}
}
}
// 选 layer 最大的 k 个点作核心(剥叶剩余部分连通),
// 第 k+1 大的 layer 值就是非核心点到核心的最大距离。
sort(layer + 1, layer + n + 1, greater<int>());
cout << layer[k + 1] << '\n';
return 0;
}复杂度
- 时间:剥叶
+ 排序 。 - 空间:邻接表与层号数组,
。
总结
"选
图示解析
这张 ASCII 图展示整道题的解题路线:
text
朴素模拟(brute.cpp)
01 序列枚举每个城市是否核心 + 连通检查 + BFS 最远距离 O(2^n)
|
| 瓶颈:组合数爆炸,必须找到核心集合的结构特征
v
关键观察(剥叶分层)
叶子 = 第 1 层;剥掉后再剥新叶子 = 第 2 层……
层号不小于某值的节点始终连通
核心 = 层号最大的 k 个节点(连通),非核心最大距离 = 第 k+1 大层号
|
v
拓扑剥叶(main.cpp)
队列维护当前叶子,动态更新度数
层号 = 被剥轮次;降序排序后取第 k+1 大
|
v
复杂度 O(n log n),空间 O(n)图中三条主线对应"暴力慢在哪"“剥叶为什么能同时给出连通核心与距离度量”“队列写法如何一遍剥完”。核心是把"选连通子集"转成"按层号取前 k 大"——连通性由剥叶过程免费保证。