题解:洛谷 P1697 Lifeguards
【题目来源】
洛谷:P1697 [USACO18JAN] Lifeguards B - 洛谷
【题目描述】
Farmer John 为他的奶牛们开设了一个游泳池,认为这将帮助它们放松并产更多的奶。
为了确保安全,他雇佣了 \(N\) 头奶牛作为救生员,每头奶牛的班次覆盖一天中的某个连续时间段。为简单起见,游泳池每天从时间 \(t=0\) 开放到时间 \(t=1000\),因此每个班次可以用两个整数描述,分别表示奶牛开始和结束其班次的时间。例如,一头救生员从时间 \(t=4\) 开始到时间 \(t=7\) 结束,覆盖了 \(3\) 个单位的时间(注意端点表示时间点)。
不幸的是,Farmer John 多雇佣了 \(1\) 名救生员,超出了他的资金支持范围。鉴于他必须解雇恰好 \(1\) 名救生员,剩下的救生员的班次能够覆盖的最长时间是多少?如果至少有一名救生员在场,则某个时间段被视为被覆盖。
【输入】
输入的第一行包含 \(N(1≤N≤100)\)。接下来的 \(N\) 行每行描述一名救生员,用两个范围在 \(0…1000\) 的整数表示该救生员班次的开始和结束时间。所有端点都是唯一的。不同救生员的班次可能会重叠。
【输出】
请输出一个数字,表示如果 Farmer John 解雇 \(1\) 名救生员后,剩下的救生员的班次能够覆盖的最长时间。
【输入样例】
3
5 9
1 4
3 7
【输出样例】
7
【核心思想】
-
问题分析:给定 \(N\) 个救生员的班次区间 \([a_i, b_i)\),必须解雇恰好 \(1\) 名,求剩余班次覆盖的最长时间。本质上是差分统计 + 枚举移除问题:先统计每个时间点被覆盖的次数,再枚举移除每个救生员,计算其独有的覆盖贡献,总覆盖减去该贡献即为移除后的覆盖时间。
-
算法选择:
- 差分数组:\(B[j]\) 记录时间点 \(j\) 被覆盖的次数
- 枚举移除:对每个救生员 \(i\),计算其覆盖范围内 \(B[j] = 1\) 的点的数量(即只有该救生员覆盖的点),总覆盖减去该值即为移除后的覆盖时间
-
关键步骤:
- 读取数据:读入 \(N\) 和 \(N\) 个区间 \([a_i, b_i)\)
- 差分统计:对每个区间 \([a_i, b_i)\),\(B[j] \leftarrow B[j] + 1\)(\(j = a_i\) 到 \(b_i-1\))
- 计算总覆盖:$sum = $ 满足 \(B[i] > 0\) 的 \(i\) 的个数(\(0 \leq i \leq 1000\))
- 枚举移除(\(i = 1\) 到 \(N\)):
- $cnt = $ 区间 \([a_i, b_i)\) 中满足 \(B[j] = 1\) 的 \(j\) 的个数
- \(ans = \max(ans, sum - cnt)\)
- 输出 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N \cdot T)\),\(T = 1000\) 为时间轴长度
- 空间复杂度:\(O(T)\),差分数组
-
差分统计的核心思想:
- 覆盖次数的量化:\(B[j]\) 表示时间点 \(j\) 被多少个救生员覆盖,\(B[j] = 1\) 意味着该点只有当前救生员覆盖
- 移除的影响:移除救生员 \(i\) 后,其独有的覆盖点(\(B[j] = 1\))将不再被覆盖,而与其他救生员共同覆盖的点(\(B[j] \geq 2\))仍然被覆盖
- 时间轴的离散性:时间范围 \(0\) 到 \(1000\) 较小,直接开数组统计即可,无需离散化
- 左闭右开区间:题目中端点表示时间点,区间 \([a, b)\) 覆盖 \(b-a\) 个单位时间
- 适用于区间覆盖、差分统计、枚举优化类问题
【算法标签】
入门 #模拟
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n; // 区间数量
int B[1005]; // 记录每个整数点被覆盖的次数
int sum; // 统计被至少覆盖一次的点的总数
int a[105], b[105]; // 存储每个区间的起始和结束点
int ans; // 最终结果(最大覆盖点数)
int cnt; // 临时计数器
int main()
{
cin >> n; // 输入区间数量
// 处理每个区间,统计覆盖情况
for (int i = 1; i <= n; i++)
{
cin >> a[i] >> b[i]; // 输入区间[a[i], b[i])
// 标记区间内的所有点(左闭右开)
for (int j = a[i]; j < b[i]; j++)
B[j]++;
}
// 计算被至少覆盖一次的点的总数
for (int i = 0; i <= 1000; i++)
if (B[i]) // 如果该点被覆盖过
sum++;
// 计算移除每个区间后能保留的最大覆盖点数
for (int i = 1; i <= n; i++)
{
cnt = 0;
// 统计当前区间中只被覆盖一次的点数
for (int j = a[i]; j < b[i]; j++)
if (B[j] == 1) // 只被当前区间覆盖的点
cnt++;
// 更新最大值:总覆盖点数 - 当前区间独有的覆盖点数
ans = max(ans, sum - cnt);
}
cout << ans << endl; // 输出结果
return 0;
}
【运行结果】
3
5 9
1 4
3 7
7
浙公网安备 33010602011771号