1. 问题背景与核心挑战
UVa 12189 "Dinner Hall"是一道经典的贪心算法练习题,源自ICPC拉丁美洲地区赛。题目模拟了一个餐厅高峰期的人员进出管理场景:给定N个客人的到达和离开时间,餐厅规定任何时刻室内人数不得超过容量限制C。我们需要计算出最多能容纳多少客人同时就餐。
这个问题的现实映射非常直观——想象一家网红餐厅在晚餐时段,不断有顾客进出。门卫需要实时监控室内人数,在即将超员时拒绝新顾客进入。我们的算法就是为这位门卫提供最优决策策略。
2. 问题建模与关键观察
2.1 输入数据特征分析
每个测试用例包含:
- 整数N(1 ≤ N ≤ 100)表示顾客数量
- 整数C(1 ≤ C ≤ 100)表示餐厅容量
- N行数据,每行两个整数A、D(0 ≤ A < D ≤ 1,000,000),分别表示顾客到达和离开时间
2.2 核心算法选择
经过分析,这个问题适合采用"事件点扫描法"(Sweep Line Algorithm):
- 将所有时间点转换为"进入"(+1)和"离开"(-1)事件
- 按时间顺序处理这些事件
- 维护当前在场人数计数器
关键技巧:当时间点相同时,必须优先处理离开事件。这避免了虚假的超员判断。
3. 算法实现详解
3.1 数据结构设计
cpp复制struct Event {
int time;
int type; // 1=arrival, -1=departure
bool operator<(const Event& other) const {
if(time != other.time) return time < other.time;
return type < other.type; // 离开事件优先
}
};
3.2 主算法流程
cpp复制int max_customers = 0, current = 0;
sort(events.begin(), events.end());
for(auto& e : events) {
current += e.type;
if(e.type == 1 && current > C) {
// 必须拒绝这个顾客
current--;
max_customers++;
}
}
3.3 边界条件处理
需要特别注意几种特殊情况:
- 同一顾客的A=D(理论上不应存在)
- 多个顾客在同一时间到达或离开
- 初始时刻餐厅为空
- C ≥ N时的优化处理
4. 复杂度分析与优化
4.1 时间复杂度
- 事件排序:O(N log N)
- 事件处理:O(N)
- 总体复杂度:O(N log N)
4.2 空间复杂度
- 存储2N个事件:O(N)
- 无需额外数据结构
5. 常见错误与调试技巧
5.1 典型错误模式
- 未正确处理同时发生的事件顺序
- 错误示例:先处理到达事件可能导致虚假超员
- 计数器更新逻辑错误
- 错误示例:在拒绝顾客后未正确减少current
- 初始条件设置不当
- 错误示例:max_customers初始化为1而非0
5.2 测试用例设计建议
text复制3 2
10 20
10 30
15 25
应输出:1(必须拒绝第三个顾客)
text复制4 2
1 5
2 6
3 7
4 8
应输出:2(多种拒绝方案)
6. 算法扩展思考
6.1 变种问题
- 加权版本:每个顾客有优先级权重
- 时间窗口限制:顾客最长停留时间
- 动态容量:餐厅容量随时间变化
6.2 实际应用场景
- 会议室预定系统
- 停车场车辆调度
- 医院床位管理
- 共享办公空间使用
7. 竞赛技巧总结
- 事件排序的cmp函数要严格定义
- 不仅比较时间,还要明确同时间的事件顺序
- 使用标准模板减少错误
- 固定使用结构体表示事件
- 画时间轴辅助理解
- 在纸上绘制案例的时间线
- 极端测试用例验证
- 全重叠/全不重叠/边界值情况
这道题的精妙之处在于用简单的事件处理机制解决了看似复杂的调度问题。在实际编程竞赛中,类似的"扫描线+事件点"思路可以解决约15%的区间调度类问题。掌握这个模式后,遇到会议室安排、课程表冲突检测等问题时都能快速套用。
我建议在理解基础解法后,可以尝试用不同的数据结构实现(如优先队列),比较它们的性能差异。另外,思考如果顾客的到达和离开时间范围扩大到10^9量级,算法需要做哪些优化?