一、先看原题:


二、题目理解:城市和信号塔
想象你站在一条长长的大马路上。
-
马路上有 n 个城市(就像 n 个小房子),每个城市有自己的"门牌号"(坐标 a[i])。
-
马路上还有 m 个信号塔(像路灯 b[j])。
-
信号塔能"发光"(信号半径 r),光可以照到左右 r 的距离。
-
一个城市,只要在某个塔的光圈里面,就能上网!
我们要做的事情就是:
⭐ 找到最小的光圈半径 r,让所有城市都能被照亮(覆盖)。
三、如何计算每个城市需要的最小光圈?
对城市 A 而言:
-
找离它最近的信号塔 B
-
两者之间的距离 = |A - B|
-
这是城市 A 需要的最小光圈
👉 但为了让所有城市都被覆盖,需要选:
⭐ 所有城市的"所需最小半径"里的最大值
四、示例(来自题目第二个样例)
城市坐标:
1 5 10 14 17
信号塔坐标:
4 11 15
把它们放在一条直线上:
C1 C2 C3 C4 C5 (C = 城市)
1 5 10 14 17
T1 T2 T3 (T = 信号塔)
4 11 15
1、第一步:找每个城市离它最近的信号塔
就像每个城市派一个小机器人去问左右两边:
"你们信号塔离我多远?"
然后选一个最近的。
2、城市 C1 的坐标是 1
它最近的信号塔是谁?
信号塔在:
4, 11, 15
分别计算距离:
| 信号塔 | 距离 | 计算方式 |
|---|---|---|
| 4 | 3 | |
| 11 | 10 | |
| 15 | 14 |
最近的是塔 4 ,距离 3。
所以:
城市1 最少需要光圈半径 = 3
3、城市 C2 的坐标是 5
最近塔是谁?
| 信号塔 | 距离 | 计算方式 |
|---|---|---|
| 4 | 1 | |
| 11 | 6 | |
| 15 | 10 |
最近的是 塔 4 ,距离 1。
城市2 最少需要光圈半径 = 1
4、城市 C3 的坐标是 10
最近塔是谁?
| 信号塔 | 距离 |
|---|---|
| 4 | 6 |
| 11 | 1 |
| 15 | 5 |
所以最近塔是 11 距离 1
城市3 最少需要光圈半径 = 1
5、城市 C4 的坐标是 14
最近塔是谁?
| 信号塔 | 距离 |
|---|---|
| 4 | 10 |
| 11 | 3 |
| 15 | 1 |
最近塔是 15 距离 1
城市4 最少需要光圈半径 = 1
6、城市 C5 在坐标是 17
最近塔是谁?
| 信号塔 | 距离 |
|---|---|
| 4 | 13 |
| 11 | 6 |
| 15 | 2 |
最近塔是 15 距离 2
城市5 最少需要光圈半径 = 2
7、每个城市都算出了自己的"需要的最小光圈"
我们整理一下:
| 城市 | 坐标 | 最近塔 | 距离(所需最小光圈) |
|---|---|---|---|
| C1 | 1 | 4 | 3 |
| C2 | 5 | 4 | 1 |
| C3 | 10 | 11 | 1 |
| C4 | 14 | 15 | 1 |
| C5 | 17 | 15 | 2 |
8、所有城市的"所需最小半径"里的最大值
计算max:
max(3, 1, 1, 1, 2) = 3
9、最终答案是:
最小光圈半径 r = 3
这表示:
当信号塔的光圈半径是 3 时,所有城市都能被照亮。
五、参考程序:
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<long long> a(n), b(m);
for (int i = 0; i < n; ++i) cin >> a[i];
for (int j = 0; j < m; ++j) cin >> b[j];
sort(a.begin(), a.end()); // 城市排序
sort(b.begin(), b.end()); // 塔排序
long long ans = 0;
int j = 0; // 指向信号塔
for (int i = 0; i < n; ++i) {
long long city = a[i];
// 移动信号塔指针,让 b[j] 尽量靠近 city
while (j + 1 < m && llabs(b[j + 1] - city) <= llabs(b[j] - city)) {
j++;
}
// b[j] 是当前最近的塔
ans = max(ans, llabs(b[j] - city));
}
cout << ans << "\n";
return 0;
}
六、程序说明:
1、变量说明:
-
n: 城市数量 -
m: 信号塔数量 -
a:长度为n的数组,存放每个城市的坐标(门牌号) -
b:长度为m的数组,存放每个信号塔的坐标 -
ans:最终答案,表示要覆盖所有城市所需的最小半径r(取整数类型long long以防坐标很大) -
i:遍历城市的索引 -
j:在塔数组b上的指针,表示当前认为"最接近当前城市"的塔的索引
2、关键步骤说明:
(1)排序:
sort(a.begin(), a.end());
sort(b.begin(), b.end());
把城市和塔都按坐标从小到大排序。
理由:排序后才能用「双指针」方法------一次从左到右遍历两个序列,效率高(线性级别)。
(2) 为何使用单个指针:
为什么用单个
j指针在所有城市之间移动,而不是每次都从头开始搜索?
如果每个城市都从头找最近塔,会重复很多工作(花时间)。但排序后城市也是从左到右的:当你处理下一个城市时,它的最近塔的索引不会比上一个城市的最近塔小太多(通常是向右移动或不动)。于是我们保留上一次找到的 j,接着往右"推进"就行了------这样总共 j 只会从 0 增到 m-1,不会回头,复杂度更低。
(3) 指针的移动:
while (j + 1 < m && llabs(b[j + 1] - city) <= llabs(b[j] - city)) j++;
这是核心:
-
比较当前塔
b[j]与下一个塔b[j+1]哪个离当前城市city更近(用绝对距离llabs)。 -
如果下一个塔更近或者距离相等,就把
j往右移动(j++),因为走到右边的塔能更好甚至一样好地覆盖当前城市。 -
用
<=(小于等于)意味着遇到"距离相同"的情况,我们也会选择走到右边去(这样j不会停在左边两个等距塔的左那一个)。这个选择不会影响正确性,只是决定了j的推进策略。
这个 while 的效果是:找到对于当前城市 city,b[j] 是最靠近 city 的塔(在当前位置 j 的条件下)。
(4) 最后max就是答案:
ans = max(ans, llabs(b[j] - city));
对于当前城市,最近塔到城市的距离就是该城市所需的最小光圈。把这个值与之前所有城市的这些"最小光圈"做最大值更新,最终 ans 就是覆盖所有城市所需的半径。
3、复杂度(为什么这么快):
-
排序
a与b:O(n log n) + O(m log m) -
主循环中,
i从0到n-1,j最多从0增到m-1,每次j增加不会回退,因此while整体执行次数 ≤m。 -
所以主循环复杂度约为
O(n + m)(在排序之后)。 -
总复杂度:
O(n log n + m log m)。
七、使用 lower_bound 的参考程序:
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<long long> a(n), b(m);
for (int i = 0; i < n; ++i) cin >> a[i];
for (int j = 0; j < m; ++j) cin >> b[j];
sort(b.begin(), b.end()); // 先把塔坐标排序
long long ans = 0;
for (int i = 0; i < n; ++i) {
long long city = a[i];
// 找到第一个不小于 city 的塔的位置
auto it = lower_bound(b.begin(), b.end(), city);
long long dist = LLONG_MAX;
if (it != b.end()) dist = min(dist, llabs(*it - city));
if (it != b.begin()) {
auto it2 = it;
--it2;
dist = min(dist, llabs(*it2 - city));
}
// 更新答案为所有城市最远的最近塔距离
ans = max(ans, dist);
}
cout << ans << "\n";
return 0;
}
1、 lower_bound 有什么用?
想象你把 所有的信号塔 按从小到大排好队。
你现在站在城市的位置上,只要向左看、向右看,就能找到:
-
离自己最近的右边的信号塔(lower_bound 找到的)
-
离自己最近的左边的信号塔(lower_bound 的前一个)
然后比较这两个哪个近即可。
lower_bound(b.begin(), b.end(), city)
表示:
到信号塔队伍里去,找到第一个 >= 城市的位置的塔。
就像你站在数字 10 的位置上,大叫:
"谁是 >= 10 的第一个信号塔?"
信号塔队伍里有人回答:
"我,我是 11!"(如果有)
2、本程序整体思路
-
读城市坐标与塔坐标
-
把塔坐标从小到大排好队
-
对每个城市:
-
用 lower_bound 找到最近的右边塔
-
再看看左边塔
-
取两个中最小的距离
-
-
取所有城市中"需要最小半径"的最大值
-
输出这个最大值
八、总结:
本题为双重贪心
-
局部贪心 (每个城市):
找当前城市最近的信号塔,保证该城市的覆盖最优。
-
全局贪心 (所有城市):
在所有城市的最近塔距离中取最大值,保证覆盖所有城市的最小最大距离。