某区域通信网络是一棵
题目
通信基站选址
给定一棵带权树,求最小覆盖半径(使得到所有节点的最大距离最小),以及所有达到该半径的候选位置。由于基站可以建在边上,最优中心可能落在某条边的内部。
任务要求
- 从标准输入读入树的节点数和边权信息;
- 使用树的直径性质在
时间内求出绝对中心; - 输出最小半径和中心位置。
输入格式
- 第一行:一个整数
( ),表示节点数; - 接下来
行,每行三个整数 ,表示节点 和 之间有一条权值为 的边。
输出格式
- 第一行:最小覆盖半径(保留 2 位小数);
- 第二行:中心位置描述
- 若中心落在节点
上,输出NODE x; - 若中心落在边
内部,距 为 ,输出EDGE u v d。
- 若中心落在节点
输出顺序规范:若中心在边上,保证
数据范围与限制
| 项目 | 范围 |
|---|---|
| 节点数 | |
| 边权 | |
| 时间复杂度要求 | |
| 额外空间限制 |
关于绝对中心
对于任意一棵树,其绝对中心(允许在边上的点)恰好是直径的中点。从直径端点
样例
样例输入 1
input
4
1 2 1
2 3 1
3 4 1样例输出 1
output
1.50
EDGE 2 3 0.50样例解释 1
树为链
直径中点距节点 2 为
| 位置 | 到 1 | 到 2 | 到 3 | 到 4 | 最大距离 |
|---|---|---|---|---|---|
| 节点 2 | 1 | 0 | 1 | 2 | 2 |
| 节点 3 | 2 | 1 | 0 | 1 | 2 |
| 边 | 1.5 | 0.5 | 0.5 | 1.5 | 1.5 |
样例输入 2
input
3
1 2 2
2 3 2样例输出 2
output
2.00
NODE 2样例解释 2
树为链
如何验证
先安装 Node.js、pnpm 和支持 C++17 的编译器。GNU Make 是首选入口,但不是强制依赖。
powershell
# 已进入本 Lab 目录
make doctor
make run
make run CASE=001-sample
make interactive
make scoreWindows 没有安装 Make 时,在仓库根目录使用完全相同的评分内核:
powershell
pnpm lab:doctor -- labs/chapter-04/exercise/E-04-18-communication-base-station
pnpm lab:run -- labs/chapter-04/exercise/E-04-18-communication-base-station
pnpm lab:run -- labs/chapter-04/exercise/E-04-18-communication-base-station --case 001-sample
pnpm lab:score -- labs/chapter-04/exercise/E-04-18-communication-base-stationmake run 在答案尚未全对时仍正常返回,避免 Make 把学习结果显示成工具故障;make score 是严格入口,只有 100 分才返回成功。标准输出参与判题,调试信息请写入标准错误。
题解
点击查看题解
思路
利用树的直径性质求绝对中心。
关键定理:对于任意树,其绝对中心(允许边上的点)恰好是直径的中点。
证明简述:设直径为
,长度 。对直径上任意点 ,若 ,则 ,离心率 ,在 时取最小值 。对任意不在直径上的点 ,设其到直径的垂足为 ,则 。
算法步骤
- 找直径端点
:任选一个起点 ,DFS/BFS 找最远点 ; - 找直径端点
并记录路径:从 出发 DFS/BFS,记录距离数组 和父节点数组,找到最远点 ,得直径长度 ; - 定位中心:从
出发沿父节点数组回溯到 ,累加边权,找到累计距离首次 的边 :- 若恰好等于
,中心为节点 ,输出NODE v; - 否则中心在边
内部,距 为 ,输出EDGE u v d(保证 )。
- 若恰好等于
复杂度分析
- 时间复杂度:
,两次 DFS/BFS 加一次路径回溯。 - 空间复杂度:
,存储邻接表、距离数组和父节点数组。
边界注意
:直径长度为 0,中心就是节点 1,半径为 0;- 精度处理:直径长度
和 可能为小数(如 时 ),建议使用double或分数运算; - 边输出顺序:务必保证
,若算法得到的是 需要交换。
点击查看参考代码
cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<vector<pair<int, int>>> adj(n + 1);
for (int i = 0; i < n - 1; ++i) {
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
if (n == 1) {
cout << "0.00\nNODE 1\n";
return 0;
}
auto bfs = [&](int%20src,%20vector%3Cll%3E&%20dist,%20vector%3Cint%3E&%20parent,%20vector%3Cint%3E&%20pw) {
dist.assign(n + 1, -1);
parent.assign(n + 1, -1);
pw.assign(n + 1, 0);
queue<int> q;
dist[src] = 0;
q.push(src);
while (!q.empty()) {
int u = q.front(); q.pop();
for (auto [v, w] : adj[u]) {
if (dist[v] == -1) {
dist[v] = dist[u] + w;
parent[v] = u;
pw[v] = w;
q.push(v);
}
}
}
};
vector<ll> dist;
vector<int> parent, pw;
bfs(1, dist, parent, pw);
int A = 1;
for (int i = 1; i <= n; ++i) if (dist[i] > dist[A]) A = i;
bfs(A, dist, parent, pw);
int B = A;
for (int i = 1; i <= n; ++i) if (dist[i] > dist[B]) B = i;
ll D = dist[B];
double halfD = D / 2.0;
vector<int> path;
for (int u = B; u != -1; u = parent[u]) path.push_back(u);
ll sum = 0;
for (int i = 0; i < (int)path.size() - 1; ++i) {
int v = path[i];
int u = path[i + 1];
int w = pw[v];
if (sum + w == D / 2 && D % 2 == 0) {
cout << fixed << setprecision(2) << halfD << "\nNODE " << u << "\n";
return 0;
}
if (sum + w > halfD) {
double d = halfD - sum;
cout << fixed << setprecision(2) << halfD << "\nEDGE ";
if (u < v) cout << u << " " << v << " " << fixed << setprecision(2) << d << "\n";
else cout << v << " " << u << " " << fixed << setprecision(2) << (w - d) << "\n";
return 0;
}
sum += w;
}
cout << fixed << setprecision(2) << halfD << "\nNODE " << B << "\n";
return 0;
}