两点 t 时刻连通等价于曼哈顿距离不超过 2t,边权取 ceil(d/2),答案为最小生成树的最大边权。
OJ: roj
题目 ID: 1437
难度:普及+/提高-
标签:最小生成树计算几何曼哈顿距离
创建: 2026-09-30 10:21
更新: 2026-10-07 11:01
形式化题目
平面上给定
正解
思路
第一步:两点何时连通?
时刻
左边由三角不等式放缩到下界
第二步:连通的时刻转化为图。
把每个点看作图上的一个顶点,给每对点连一条无向边,边权为
在时刻
样例验证:两点
实现:
代码
Python 版:
#!/usr/bin/env python3
# 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-07-05 21:47
# update_at: 2026-10-04 14:47
# 题目: roj 1437 扩散
# 结论: 时刻 t 两菱形(切比雪夫转曼哈顿菱形)相交 <=> 曼哈顿距离 <= 2t
# 边权 w = ceil(dist/2), 答案 = 最小生成树的最大边权
# 复杂度: O(n^2) 建完全图, Prim 求最小生成树, n<=50
import sys
data = iter(map(int, sys.stdin.buffer.read().split()))
n = next(data)
P = [(next(data), next(data)) for _ in range(n)] # 每行一个点的坐标 (x, y)
if n == 1:
print(0) # 只有一个点, 时刻 0 即连通
raise SystemExit
a, b = P[0]
d = [abs(a - c) + abs(b - e) for c, e in P] # Prim 初始: 点 0 到各点的距离
used, ans = [False] * n, 0
for _ in range(n): # 做 n 轮, 每轮选出离生成树最近的点
k = min(range(n), key=lambda i: (used[i], d[i])) # 未使用的里挑最近的
used[k], ans = True, max(ans, d[k]) # 并入生成树, 更新树的最大边
d = [min(d[i], abs(P[i][0] - P[k][0]) + abs(P[i][1] - P[k][1])) if not used[i] else d[i]
for i in range(n)] # 用新点 k 松弛其余点
print(-(-ans // 2)) # ceil(ans/2): 边权 d 的扩散时刻C++ 版(同一算法):
/**
* 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-10-05 01:08
* update_at: 2026-10-05 01:08
*/
// roj 1437 扩散
// 结论: 时刻 t 两个扩散菱形(曼哈顿距离意义)相交 <=> 曼哈顿距离 <= 2t
// 于是把边权看作两点曼哈顿距离, 答案 = 最小生成树最大边权的一半向上取整
// 做法: 完全图稠密, n<=50, 用 O(n^2) 的 Prim 求最小生成树
#include <cstdio>
using namespace std;
typedef long long ll;
const int MAXN = 55;
ll px[MAXN]; // 第 i 个点的横坐标
ll py[MAXN]; // 第 i 个点的纵坐标
ll min_dist[MAXN]; // min_dist[i] = 第 i 个点到当前生成树的最小曼哈顿距离
bool used[MAXN]; // used[i] = 点 i 是否已经并入生成树
ll abs_ll(ll v) {
if (v < 0) {
return -v;
}
return v;
}
// 两点曼哈顿距离
ll manhattan(int i, int j) {
return abs_ll(px[i] - px[j]) + abs_ll(py[i] - py[j]);
}
int main() {
ll n;
scanf("%lld", &n);
for (ll i = 1; i <= n; i++) {
scanf("%lld%lld", &px[i], &py[i]);
}
// Prim: 先把点 1 放入生成树, 其余点的距离初始为到点 1 的曼哈顿距离
for (ll i = 1; i <= n; i++) {
min_dist[i] = manhattan(1, i);
}
used[1] = true;
ll max_edge = 0; // 生成树中最大的边权(曼哈顿距离形式)
for (ll round = 1; round <= n - 1; round++) {
// 在未使用的点里找离生成树最近的点
ll best = 0;
for (ll i = 1; i <= n; i++) {
if (used[i]) {
continue;
}
if (best == 0 || min_dist[i] < min_dist[best]) {
best = i;
}
}
used[best] = true;
if (min_dist[best] > max_edge) {
max_edge = min_dist[best];
}
// 用新并入的点 best 松弛其余未使用的点
for (ll i = 1; i <= n; i++) {
if (used[i]) {
continue;
}
ll d = manhattan(i, best);
if (d < min_dist[i]) {
min_dist[i] = d;
}
}
}
// 边权为曼哈顿距离 d, 对应扩散时刻为 ceil(d/2)
ll ans = (max_edge + 1) / 2;
printf("%lld\n", ans);
return 0;
}复杂度
- 建边隐式在距离计算中:Prim 共
轮,每轮 更新距离,总时间 (完全图边数本身 ,与 Kruskal 排序的 同阶或更优)。 - 空间
(只存每个点到生成树的最短距离,不需要显式存边)。
总结
- 扩散区域是曼哈顿距离意义下的菱形,两菱形相交
两点曼哈顿距离 。 - 「所有点连通的最小时刻」= 完全图(边权
)上最小生成树的最大边权,这是经典的「瓶颈生成树」转化。 - 稠密图 Prim
恰好匹配 的规模。
图示解析
两个时刻
flowchart LR
subgraph T1["t = 2: 菱形相距尚远, 未连通"]
A1["点 a 的菱形<br/>半径 2"]
B1["点 b 的菱形<br/>半径 2"]
A1 -. "d=10 > 2t=4" .-> B1
end
subgraph T2["t = 5: 菱形相切, 首次连通"]
A2["点 a 的菱形<br/>半径 5"]
B2["点 b 的菱形<br/>半径 5"]
A2 == "d=10 <= 2t=10" ==> B2
end
多点情形下,每个点向其余点连边权为
flowchart TD
A["点 0 (0,0)"] -- "ceil(10/2)=5" --- B["点 1 (5,5)"]
A -- "ceil(3/2)=2" --- C["点 2 (1,2)"]
C -- "ceil(7/2)=4" --- B
上图中 MST 取边
