2025年海淀区中小学信息学竞赛复赛(小学组试题第六题 蜂窝网络 (net))

一、先看原题:


二、题目理解:城市和信号塔

想象你站在一条长长的大马路上。

  • 马路上有 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 的效果是:找到对于当前城市 cityb[j]最靠近 city 的塔(在当前位置 j 的条件下)


(4) 最后max就是答案:

ans = max(ans, llabs(b[j] - city));

对于当前城市,最近塔到城市的距离就是该城市所需的最小光圈。把这个值与之前所有城市的这些"最小光圈"做最大值更新,最终 ans 就是覆盖所有城市所需的半径。


3、复杂度(为什么这么快):

  • 排序 abO(n log n) + O(m log m)

  • 主循环中,i0n-1j 最多从 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 找到最近的右边塔

    • 再看看左边塔

    • 取两个中最小的距离

  • 取所有城市中"需要最小半径"的最大值

  • 输出这个最大值


八、总结:

本题为双重贪心

  1. 局部贪心 (每个城市):

    找当前城市最近的信号塔,保证该城市的覆盖最优。

  2. 全局贪心 (所有城市):

    在所有城市的最近塔距离中取最大值,保证覆盖所有城市的最小最大距离。

相关推荐
xiaoye-duck1 小时前
C++入门基础指南:命名空间namespace
c++
4311媒体网1 小时前
php和c++哪个更好学?C++难学吗?
java·c++·php
修炼地1 小时前
代码随想录算法训练营第二十七天 | 56. 合并区间、738.单调递增的数字、968.监控二叉树
c++·算法
仰泳的熊猫1 小时前
1031 Hello World for U
数据结构·c++·算法·pat考试
liu****1 小时前
12.C语言内存相关函数
c语言·开发语言·数据结构·c++·算法
FMRbpm2 小时前
栈练习--------从链表中移除节点(LeetCode 2487)
数据结构·c++·leetcode·链表·新手入门
编程小Y2 小时前
C++ 静态库与动态库
c++
不秃头的帅哥2 小时前
程序地址空间(基于c++和linxu的一些个人笔记
linux·开发语言·c++·操作系统·内存空间
Tandy12356_2 小时前
手写TCP/IP协议栈——无回报ARP包生成
c语言·c++·tcp/ip·计算机网络