把每个小岛能被雷达覆盖的雷达位置转成 x 轴上的区间,按右端点排序做区间选点贪心,无解当且仅当某岛纵坐标超过 d。
OJ: roj
题目 ID: 3019
难度:提高
标签:贪心区间贪心排序几何python
创建: 2026-10-01 10:25
更新: 2026-10-07 13:50
形式化题目
给定
正解
思路
第一步:把"雷达"翻译成"区间"。
一个位于
也就是说,能覆盖这个小岛的所有雷达位置,恰好构成
到此,原问题变成一个纯粹的区间问题:
平面上有
条线段,选最少的点,使得每条线段都至少包含一个选出的点。
第二步:区间选点的经典贪心。
朴素想法是枚举所有候选点(每条线段的
经典贪心:把
- 若当前线段的左端点
,说明它与之前所有已放雷达都接触不到,必须新开一个雷达;贪心把它放在这条线段的右端点 处(越靠右,越有机会顺带覆盖后面还没处理的线段),并更新 ,答案加一。 - 若
,当前线段已被 处的雷达覆盖,直接跳过。
为什么按右端点排序 + 放右端点是最优的? 用交换论证:考虑排序后第一条未被覆盖的线段,设其右端点为
样例走一遍(
| 小岛 | 坐标 | 可放雷达区间 |
|---|---|---|
| A | ||
| B | ||
| C |
按右端点排序为 B(
- 扫到 B:
,新放雷达于 ,答案 ; - 扫到 A:
,与 B 处雷达不相交,新放雷达于 ,答案 ; - 扫到 C:
,已被覆盖,跳过。
最终答案
flowchart LR
subgraph 原问题
I["小岛 (x, y) + 半径 d"]
end
subgraph 转化
S["n 条闭区间 x±√(d²−y²)"]
end
subgraph 贪心
G["按右端点排序<br/>l > pos 则新放雷达于 r"]
end
I -- "雷达 p 覆盖岛 ⟺ p ∈ [x−t, x+t]" --> S
S -- "最少选点覆盖所有区间" --> G
代码
Python 版:
import sys
from math import sqrt
def main() -> None:
data = sys.stdin.read().split()
it = iter(data)
n, d = int(next(it)), int(next(it))
segs = []
for _ in range(n):
x, y = int(next(it)), int(next(it))
if y > d: # 纵坐标超过 d,任何雷达都够不着
print(-1)
return
t = sqrt(d * d - y * y) # 半径 d 的圆与 x 轴交点的半宽
segs.append((x - t, x + t)) # 小岛可被覆盖 ⟺ 雷达位置落在此区间内
segs.sort(key=lambda s: s[1]) # 按右端点排序后做区间选点贪心
ans, pos = 0, float("-inf") # pos 为上一个已放雷达的位置
for l, r in segs:
if l > pos: # 当前区间与已有雷达不相交,必须新放一个
ans += 1
pos = r # 贪心放在右端点,能覆盖后续最多的区间
print(ans)
main()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-06 14:26
* update_at: 2026-10-06 14:26
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
typedef long long ll;
ll n; // 小岛数目
ll d; // 雷达检测范围(半径)
struct Interval {
double l; // 能覆盖该岛的雷达位置左端点 x - sqrt(d^2 - y^2)
double r; // 能覆盖该岛的雷达位置右端点 x + sqrt(d^2 - y^2)
};
Interval seg[MAXN]; // seg[i] 为第 i 个小岛对应的一维可选雷达位置区间
// 区间选点贪心:按右端点从小到大排序。
bool cmp(const Interval &a, const Interval &b) {
return a.r < b.r;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> d;
bool impossible = false; // 是否存在纵坐标超过 d、任何雷达都够不着的小岛
for (ll i = 1; i <= n; i++) {
ll x, y;
cin >> x >> y;
if (y > d) {
impossible = true;
continue;
}
double rest = d * d - y * y; // 先存成 double,避免二次开方前出现类型不匹配
double t = sqrt(rest); // 雷达位置允许相对 x 偏移的最大距离
seg[i].l = x - t;
seg[i].r = x + t;
}
if (impossible) {
cout << -1 << "\n";
return 0;
}
sort(seg + 1, seg + n + 1, cmp);
ll ans = 0;
double pos = -1e18; // 上一个已放置雷达的位置,初始为负无穷
for (ll i = 1; i <= n; i++) {
if (seg[i].l > pos) { // 当前区间与已有雷达不相交,必须新放一个
ans++;
pos = seg[i].r; // 贪心放在右端点,能覆盖后续最多的区间
}
}
cout << ans << "\n";
return 0;
}复杂度
- 时间复杂度:排序
,扫描 ,总计 。 下远在限时时限之内。 - 空间复杂度:
存储区间。 - 浮点精度:
时开方不会出现定义域问题;区间端点用浮点存储,坐标范围不超过 ,双精度误差远小于区间相交判断需要的精度(判断的是 的严格不等,端点重合时取等也能被同一雷达覆盖,不影响答案)。
总结
- 核心转化:雷达覆盖小岛
雷达位置落在以岛为中心的对称区间 内,几何覆盖问题变成区间选点问题。 - 算法:按右端点排序,
时在右端点新放雷达,贪心正确性由交换论证保证。 - 无解判定:只要有一个岛
输出 。 - 复杂度
,是最少点覆盖所有区间的经典模型(与"区间调度/选最少区间"同族)。