题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(N\) 个救生员的班次区间 \([a_i, b_i)\),必须解雇恰好 \(1\) 名,求剩余班次覆盖的最长时间。本质上是差分统计 + 枚举移除问题:先统计每个时间点被覆盖的次数,再枚举移除每个救生员,计算其独有的覆盖贡献,总覆盖减去该贡献即为移除后的覆盖时间。

  2. 算法选择

    • 差分数组\(B[j]\) 记录时间点 \(j\) 被覆盖的次数
    • 枚举移除:对每个救生员 \(i\),计算其覆盖范围内 \(B[j] = 1\) 的点的数量(即只有该救生员覆盖的点),总覆盖减去该值即为移除后的覆盖时间
  3. 关键步骤

    • 读取数据:读入 \(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \cdot T)\)\(T = 1000\) 为时间轴长度
    • 空间复杂度:\(O(T)\),差分数组
  5. 差分统计的核心思想

    • 覆盖次数的量化\(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
posted @ 2026-08-17 09:45  团爸讲算法  阅读(14)  评论(0)    收藏  举报