1. 项目概述:UVa 12189 Dinner Hall问题解析
这道来自UVa在线判题系统的题目编号12189,标题直译为"晚餐大厅",属于典型的算法竞赛中的调度优化类问题。我第一次遇到这个问题是在准备ICPC区域赛时,它看似简单的表面下隐藏着几个需要仔细处理的边界条件。
这类餐厅调度问题在实际生活中很常见——比如自助餐厅需要统计高峰时段人流量,或者活动场地要控制同时在场人数。题目通常会给出客人的到达和离开时间,要求计算同一时间段内在场人数的峰值。虽然核心是区间处理,但魔鬼藏在细节里。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题建模与输入分析
2.1 输入数据格式规范
题目输入由多个测试用例组成,每个用例以整数N(1 ≤ N ≤ 50000)开头表示客人数量。接下来N行每行包含三个数据:到达时间、离开时间和用餐类型(用1或2表示)。时间格式为HHMM的24小时制,且保证离开时间>到达时间。
关键细节:用餐类型1表示该客人会在餐厅用餐,类型2表示只是取餐带走。只有类型1的客人才会计入大厅人数统计。
2.2 核心问题转化
我们需要找出餐厅内同时用餐人数的最大值。这本质上是一个区间重叠计数问题——将每个用餐客人的停留时间看作一个区间[arrival, departure],计算这些区间在时间轴上的最大重叠度。
3. 算法设计与优化
3.1 基础解法:事件点排序法
最直观的方法是使用"事件点"思路:
- 为每个客人创建两个事件点:(arrival, +1)和(departure, -1)
- 将所有事件点按时间排序(时间相同时,离开事件优先)
- 扫描排序后的事件序列,维护当前人数计数器
python复制def max_diners(guests):
events = []
for arr, dep, typ in guests:
if typ == 1: # 只统计堂食客人
events.append((arr, 1))
events.append((dep, -1))
events.sort(key=lambda x: (x[0], x[1]))
max_count = 0
current = 0
for time, delta in events:
current += delta
max_count = max(max_count, current)
return max_count
3.2 时间复杂度分析
排序阶段是O(N log N),扫描阶段是O(N)。对于N≤50000的数据规模完全可行。我曾在一次比赛中尝试用O(N²)的暴力解法,结果当然TLE(时间超过)了。
4. 关键难点与边界处理
4.1 时间比较的特殊情况
HHMM格式的时间比较需要特别注意:
- "0900"应该表示为整数900
- "0059"和"0100"的比较(59 < 100)
- 跨午夜的情况(题目通常保证不出现)
建议统一转换为分钟数:
python复制def time_to_minutes(t):
return (t // 100) * 60 + (t % 100)
4.2 同时发生的事件处理
当多个事件发生在同一时间点时,必须确保离开事件(-1)先于到达事件(+1)处理,否则会短暂高估人数。这是很多选手第一次提交时WA(答案错误)的主要原因。
4.3 内存优化技巧
对于大型输入,可以边读取边处理事件点,避免存储所有客人数据。这在C++等语言中能显著减少内存使用。
5. 完整解决方案实现
5.1 Python参考代码
python复制def solve():
import sys
input = sys.stdin.read
data = input().split()
index = 0
while True:
N = int(data[index])
if N == 0:
break
index += 1
events = []
for _ in range(N):
arr = int(data[index])
dep = int(data[index+1])
typ = int(data[index+2])
index += 3
if typ == 1:
arr_min = (arr // 100) * 60 + (arr % 100)
dep_min = (dep // 100) * 60 + (dep % 100)
events.append((arr_min, 1))
events.append((dep_min, -1))
events.sort(key=lambda x: (x[0], x[1]))
max_diners = current = 0
for _, delta in events:
current += delta
if current > max_diners:
max_diners = current
print(max_diners)
solve()
5.2 C++优化版本
cpp复制#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int convertTime(int t) {
return (t/100)*60 + (t%100);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N;
while (cin >> N, N != 0) {
vector<pair<int, int>> events;
for (int i = 0; i < N; ++i) {
int arr, dep, typ;
cin >> arr >> dep >> typ;
if (typ == 1) {
int arr_min = convertTime(arr);
int dep_min = convertTime(dep);
events.emplace_back(arr_min, 1);
events.emplace_back(dep_min, -1);
}
}
sort(events.begin(), events.end(), [](const auto& a, const auto& b) {
return a.first != b.first ? a.first < b.first : a.second < b.second;
});
int current = 0, max_diners = 0;
for (const auto& e : events) {
current += e.second;
max_diners = max(max_diners, current);
}
cout << max_diners << '\n';
}
return 0;
}
6. 测试用例与调试技巧
6.1 典型测试案例
code复制输入示例:
4
800 900 1
830 930 1
900 1000 1
930 1030 1
0
输出示例:
2
6.2 特殊边界案例
code复制1. 单个客人:
1
1200 1300 1
→ 应输出1
2. 所有客人都是外带:
2
800 900 2
830 930 2
→ 应输出0
3. 时间完全重叠:
3
900 1000 1
900 1000 1
900 1000 1
→ 应输出3
6.3 调试建议
- 打印事件点序列,检查排序是否正确
- 验证时间转换函数是否正确处理了"0059"这类时间
- 检查是否漏掉了typ==2的过滤条件
7. 算法扩展与变种思考
7.1 支持跨午夜的情况
如果题目允许跨午夜(如2300-0100),需要特殊处理:
python复制def convert_time(t):
hours = t // 100
minutes = t % 100
return hours * 60 + minutes
if dep < arr: # 跨午夜
events.append((convert_time(arr), 1))
events.append((convert_time(2400), -1))
events.append((convert_time(0), 1))
events.append((convert_time(dep), -1))
7.2 多日数据分析
如果需要分析连续多天的数据,可以将所有时间转换为从第一天开始的绝对分钟数,然后应用相同的算法。
7.3 实时人数监控
在实际系统中,可以使用线段树或Fenwick树来支持动态查询任意时刻的人数,这在需要实时监控的场景下非常有用。
