题目描述
一条笔直的公路上装了 N 个路灯,从位置 0 开始,路灯之间间距固定为 100 米。
每个路灯有自己的照明半径。请计算第一个路灯和末尾一个路灯之间,无法照明的区间的长度和。
N 不超过 10 万,照明半径最大为 10^7。
讲个故事:夜跑爱好者的噩梦
小李喜欢夜跑。这条路上每隔 100 米有一盏路灯,但亮度不一,有的照得远,有的照得近。
他想知道:从第一盏路灯跑到末尾一盏,路上有多少段黑漆漆的地方?
这不就是把所有光照范围画出来,看看中间漏了多少缝吗?
核心原理:区间覆盖求 gaps
路灯位置是固定的:0,100,200,…,(N - 1) * 100。
第 i 盏路灯的照明范围是:
[i * 100 - r[i], i * 100 + r[i]]
把所有区间合并后,用总长度减去被覆盖的长度,就是答案。
更直接的做法:
- 生成所有区间
- 按左端点排序
- 逐个合并,记录合并后的覆盖范围
- 合并过程中,相邻覆盖区间之间的缝隙就是未覆盖长度
怎么实现?
- 读入
N和半径数组r - 生成区间
[i*100 - r[i], i*100 + r[i]] - 按左端点排序
- 维护当前合并区间
[curL, curR]- 如果下一个区间左端点 <= curR,说明有重叠,更新 curR = max(curR, right)
- 否则,说明出现缝隙,ans += 缝隙长度,然后开启新区间
- 注意只统计
[0, (N-1)*100]范围内的缝隙
代码实现
C 语言
#include <stdio.h>
#include <stdlib.h>
typedef struct {
long long l, r;
} Interval;
int cmp(const void *a, const void *b) {
Interval *x = (Interval *)a, *y = (Interval *)b;
if (x->l != y->l) return x->l < y->l ? -1 : 1;
return x->r < y->r ? -1 : 1;
}
long long solve(int n, long long r[]) {
Interval ivs[100005];
for (int i = 0; i < n; i++) {
long long pos = i * 100LL;
ivs[i].l = pos - r[i];
ivs[i].r = pos + r[i];
}
qsort(ivs, n, sizeof(Interval), cmp);
long long totalLen = (n - 1) * 100LL;
long long covered = 0;
long long curL = ivs[0].l, curR = ivs[0].r;
for (int i = 1; i < n; i++) {
if (ivs[i].l <= curR) {
if (ivs[i].r > curR) curR = ivs[i].r;
} else {
covered += curR - curL;
curL = ivs[i].l;
curR = ivs[i].r;
}
}
covered += curR - curL;
// 只考虑 [0, totalLen] 区间
if (curR > totalLen) covered -= curR - totalLen;
if (ivs[0].l < 0) covered -= 0 - ivs[0].l;
return totalLen - covered;
}
C++
#include <bits/stdc++.h>
using namespace std;
long long solve(int n, vector<long long> r) {
vector<pair<long long, long long>> ivs;
for (int i = 0; i < n; i++) {
long long pos = i * 100LL;
ivs.push_back({pos - r[i], pos + r[i]});
}
sort(ivs.begin(), ivs.end());
long long totalLen = (n - 1) * 100LL;
long long curL = ivs[0].first, curR = ivs[0].second;
long long covered = 0;
for (int i = 1; i < n; i++) {
auto [l, rr] = ivs[i];
if (l <= curR) {
curR = max(curR, rr);
} else {
covered += curR - curL;
curL = l;
curR = rr;
}
}
covered += curR - curL;
// 裁剪到 [0, totalLen]
covered = min(covered, totalLen);
return totalLen - covered;
}
Java
import java.util.*;
public class Main {
public static long solve(int n, long[] r) {
long[][] ivs = new long[n][2];
for (int i = 0; i < n; i++) {
long pos = i * 100L;
ivs[i][0] = pos - r[i];
ivs[i][1] = pos + r[i];
}
Arrays.sort(ivs, Comparator.comparingLong(a -> a[0]));
long totalLen = (n - 1) * 100L;
long curL = ivs[0][0], curR = ivs[0][1];
long covered = 0;
for (int i = 1; i < n; i++) {
long l = ivs[i][0], rr = ivs[i][1];
if (l <= curR) {
curR = Math.max(curR, rr);
} else {
covered += curR - curL;
curL = l;
curR = rr;
}
}
covered += curR - curL;
// 限制在 [0, totalLen] 内
long ans = totalLen - covered;
return Math.max(ans, 0);
}
}
JavaScript
function solve(n, r) {
const ivs = [];
for (let i = 0; i < n; i++) {
const pos = i * 100;
ivs.push([pos - r[i], pos + r[i]]);
}
ivs.sort((a, b) => a[0] - b[0]);
const totalLen = (n - 1) * 100;
let curL = ivs[0][0], curR = ivs[0][1];
let covered = 0;
for (let i = 1; i < n; i++) {
const [l, rr] = ivs[i];
if (l <= curR) {
curR = Math.max(curR, rr);
} else {
covered += curR - curL;
curL = l;
curR = rr;
}
}
covered += curR - curL;
return Math.max(totalLen - covered, 0);
}
Python
def solve(n, r):
ivs = []
for i in range(n):
pos = i * 100
ivs.append((pos - r[i], pos + r[i]))
ivs.sort()
total_len = (n - 1) * 100
cur_l, cur_r = ivs[0]
covered = 0
for l, rr in ivs[1:]:
if l <= cur_r:
cur_r = max(cur_r, rr)
else:
covered += cur_r - cur_l
cur_l, cur_r = l, rr
covered += cur_r - cur_l
return max(total_len - covered, 0)
复杂度分析
- 生成区间:
O(n) - 排序:
O(n * log n) - 合并区间:
O(n) - 总时间复杂度:
O(n * log n) - 空间复杂度:
O(n)
总结一下
路灯照明问题 = 区间合并求未覆盖长度。
套路:
- 把每个路灯的照明范围转成区间
- 按左端点排序
- 合并重叠区间
- 用总长度减覆盖长度
注意边界:题目只关心第一盏到末尾一盏路灯之间的区间,别被路灯照到外面去的长度干扰。
你平时遇到区间问题,是先排序还是先扫描?欢迎在评论区聊聊。
479

被折叠的 条评论
为什么被折叠?



