洛谷 P1803 凌乱的yyy / 线段覆盖 详细题解

一、 题目本质与贪心策略剖析

这是一道经典的“活动选择问题”(Activity Selection Problem)。题目要求我们在给定的 $n$ 个时间段中,挑选出尽可能多的互不重叠的区间。

面对这类“求最多/最少”的问题,贪心算法是最佳选择。我们需要思考的是:**每次选择哪个区间,才能保证后续能塞进更多的区间?**
直觉告诉我们:应该优先选择**结束时间最早**的比赛。因为一个比赛结束得越早,它留给后续比赛的时间就越多,从而最大化了后续的选择空间。

贪心策略总结:

1. **排序**:将所有区间按照“结束时间(右端点)”从小到大进行升序排序。
2. **选择**:从前往后遍历,只要当前区间的“开始时间”大于或等于上一个被选中区间的“结束时间”,就将其选中,并更新结束时间。

二、 为什么这个贪心策略是正确的?(严谨证明)

很多同学会有疑问:为什么不能按开始时间排序?为什么不能按区间长度排序?
这里我们可以用**交换论证法**来证明“按结束时间排序”的绝对正确性:
假设存在一个最优解,它选择的第一个区间并不是结束时间最早的。我们可以将这个最优解中的第一个区间,替换成我们贪心策略选出的“结束时间最早的区间”。
因为贪心选出的区间结束得更早,所以它替换后,不仅不会与后面的区间产生冲突,反而给后面的区间留出了更多的时间。这意味着,替换后的解依然是一个最优解。通过不断进行这样的交换,我们最终可以将任何最优解都转化为贪心解。这就证明了贪心解就是全局最优解。

三、 算法执行流程拆解

1. **数据读入与存储**:使用结构体数组存储每个比赛的开始时间 `start` 和结束时间 `end`。
2. **自定义排序**:编写比较函数 `cmp`,严格按照 `x.end < y.end` 进行排序。
3. **初始化**:排序完成后,第一个比赛(结束最早的)必定被选中,因此初始答案 `ans = 1`,并记录当前结束时间 `last_end = a[1].end`。
4. **线性遍历**:从第 2 个比赛开始向后扫描。判断条件为 `a[i].start >= last_end`。如果满足,说明时间不冲突,答案加 1,并更新 `last_end`。

四、 C++ 完整代码与细节注释

#include<bits/stdc++.h>
using namespace std;

// 竞赛常用技巧:将 int 重定义为 long long,防止中间计算溢出
#define int long long 

// 定义区间结构体
struct node {
    int start; // 开始时间
    int end;   // 结束时间
};

// 自定义比较函数:按照结束时间从小到大排序
bool cmp(node x, node y) {
    return x.end < y.end; 
}

// 题目数据范围 n <= 10^6,数组必须开够大,防止越界
node a[1000010]; 

// 使用了 #define int long long 后,主函数必须用 signed main
signed main(){
    int n;
    cin >> n;
    
    // 1. 读入数据
    for(int i = 1; i <= n; i++){
        cin >> a[i].start >> a[i].end;
    }
    
    // 2. 按照结束时间进行 O(n log n) 的快速排序
    sort(a + 1, a + 1 + n, cmp);
    
    // 3. 贪心选择过程
    int ans = 1;              // 第一个结束最早的比赛必选
    int last_end = a[1].end;  // 记录当前选中比赛的结束时间
    
    for(int i = 2; i <= n; i++){
        // 如果当前比赛的开始时间 >= 上一场结束时间,说明不冲突
        if(a[i].start >= last_end){
            ans++;            // 参加该比赛,计数加 1
            last_end = a[i].end; // 更新结束时间,准备看下一场
        }
    }
    
    cout << ans;
    return 0;
}

五、 核心避坑指南

1. **数组越界(RE)**:题目明确提示 $1 \le n \le 10^6$,在 C++ 中全局数组 `a[1000010]` 是必须的,开小了会直接运行错误。
2. **时间复杂度(TLE)**:数据量达到百万级别,排序必须使用 `std::sort`($O(n \log n)$),绝对不能手写冒泡或选择排序($O(n^2)$)。
3. **边界条件**:判断不冲突的条件是 `>=` 而不是 `>`。题目允许一个比赛刚结束,另一个比赛立刻开始(例如 `[0, 2]` 和 `[2, 4]` 是可以同时参加的)。
4. **主函数声明**:当使用了 `#define int long long` 宏定义时,标准的 `int main()` 会编译报错,必须写成 `signed main()`。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值