1. 题目背景与需求解析
这道题目来自信息学奥赛经典教材《信息学奥赛一本通》第1537页,属于基础算法训练中的区间处理类问题。题目描述的是在校门外有一条长度为L的马路上种了一排树,初始时所有树都完好无损。现在有多个施工队要在不同区间内移走树木,需要计算最终剩下的树木数量。
这类问题在实际编程竞赛中非常典型,主要考察以下几个核心能力:
- 对连续区间标记的处理技巧
- 数组或数据结构的灵活运用
- 边界条件的处理能力
- 算法时间复杂度的把控
2. 解题思路分析与算法选择
2.1 暴力解法与优化思路
最直观的解法是创建一个长度为L+1的布尔数组(因为包括0位置),初始值设为true表示有树。然后对于每个施工区间,将对应位置标记为false。最后统计数组中true的数量。
这种方法的时间复杂度是O(L×M),其中L是马路长度,M是施工次数。当L和M较大时(比如L=1e5,M=100),这种解法在竞赛中会超时。
2.2 区间合并优化算法
更高效的解法是使用区间合并:
- 将所有施工区间按起点排序
- 合并重叠或相邻的区间
- 计算合并后各区间的总长度
- 用总马路长度减去被移走的总长度
这种方法的时间复杂度主要来自排序步骤,为O(M log M),能高效处理大规模数据。
3. 代码实现与细节处理
3.1 基础实现(C++版本)
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { int L, M; cin >> L >> M; vector<pair<int, int>> intervals(M); for(int i=0; i<M; i++) { cin >> intervals[i].first >> intervals[i].second; } // 按区间起点排序 sort(intervals.begin(), intervals.end()); // 合并区间 vector<pair<int, int>> merged; for(auto interval : intervals) { if(merged.empty() || interval.first > merged.back().second) { merged.push_back(interval); } else { merged.back().second = max(merged.back().second, interval.second); } } // 计算被移走的总长度 int removed = 0; for(auto m : merged) { removed += m.second - m.first + 1; } cout << L + 1 - removed << endl; return 0; }3.2 关键细节说明
- 区间表示:题目中的区间是闭区间,包含两端点
- 树木计数:包括位置0,所以总树数是L+1
- 合并条件:当前区间起点在上个合并区间终点之后才不重叠
- 边界处理:注意区间可能从0开始
4. 算法优化与变种思考
4.1 差分数组解法
另一种高效解法是使用差分数组:
- 创建长度为L+2的差分数组(多一位方便处理)
- 对每个区间[start, end],执行diff[start]++,diff[end+1]--
- 计算前缀和,统计未被标记的位置数
这种方法时间复杂度O(L+M),空间复杂度O(L),适合L不太大的情况。
4.2 线段树解法
对于更复杂的变种题目(如动态查询、区间修改等),可以使用线段树:
- 建立覆盖0-L的线段树
- 每个节点记录区间内剩余树木数
- 支持区间更新和查询操作
5. 常见错误与调试技巧
5.1 典型错误案例
- 数组越界:忘记处理位置0或L
- 区间合并错误:没有正确处理相邻区间
- 计数错误:总树数应该是L+1而非L
- 输入处理:多个测试用例时未正确初始化
5.2 调试建议
- 小数据测试:手工计算验证
- 边界测试:L=0,M=0等特殊情况
- 打印中间结果:输出合并后的区间检查
- 使用assert:验证关键假设
6. 题目变种与扩展训练
6.1 变种题目示例
- 多次查询:在施工过程中随时查询某段区间剩余树木数
- 树木恢复:某些区间会重新种树
- 多维扩展:扩展到二维平面上的树木分布
- 动态统计:实时统计最长连续树木区间
6.2 推荐练习题目
- 区间覆盖问题(POJ 1089)
- 会议室安排问题(LeetCode 252)
- 合并区间(LeetCode 56)
- 插入区间(LeetCode 57)
7. 竞赛技巧与经验分享
- 数据范围分析:首先关注L和M的范围,决定算法选择
- 空间优化:对于L很大的情况,优先考虑区间合并而非差分数组
- 编码习惯:统一使用0-based或1-based,避免混淆
- 模板准备:提前准备好区间合并的代码模板
在实际比赛中,这类基础题目必须快速准确地解决。建议熟练掌握2-3种解法,根据具体数据范围选择最优实现。对于初学者,建议从暴力解法开始,理解问题本质后再逐步优化。