一、 题目本质与贪心策略剖析
这是一道经典的“活动选择问题”(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()`。

751

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



