PTA天梯赛:L1-043 阅览室 (从状态机视角解析借还逻辑)

张开发
2026/4/20 22:22:22 15 分钟阅读

分享文章

PTA天梯赛:L1-043 阅览室 (从状态机视角解析借还逻辑)
1. 从状态机视角理解阅览室借还逻辑第一次看到PTA天梯赛L1-043这道阅览室题目时很多人会直接想到用数组记录每本书的借还状态。但实际编码时会发现各种边界情况让人头疼——比如同一本书被反复借出、跨天的借还记录、无效操作的处理等。这时候用状态机的思维来建模问题会让整个逻辑变得异常清晰。状态机本质上就是把对象的行为抽象成几个明确的状态以及触发状态转换的条件。在这个阅览室场景中每本书可以定义三种基本状态未借状态IDLE初始状态等待被借阅已借状态BORROWED成功借出后进入的状态待还状态PENDING_RETURN特殊状态用于处理异常流程举个例子当收到借书请求S指令时如果当前状态是未借IDLE则转换为已借BORROWED如果已经是已借状态则保持原状态相当于忽略重复借阅这种建模方式最大的优势是能可视化复杂逻辑。我画过一张状态转换图用箭头表示各种操作触发的状态变化瞬间就理清了所有边界情况。比如处理昨天借今天还的问题只需要在状态转换时额外检查时间戳是否同一天即可。2. 关键状态转换的条件设计状态机的核心在于明确定义转换规则。在实际编码中我用结构体存储了三个关键字段struct Book { int borrow_day; // 记录借阅日期 int borrow_h; // 借阅小时 int borrow_m; // 借阅分钟 int state; // 当前状态 };借书操作S指令的转换逻辑检查当前状态如果是未借STATE_IDLE更新借阅时间并转为已借STATE_BORROWED如果是已借不做任何处理避免重复借阅记录操作当天的日期标记用于后续还书时的日期校验还书操作E指令的转换逻辑必须满足三个条件才视为有效还书当前状态为已借STATE_BORROWED借阅日期与还书日期相同图书编号有效1-1000满足条件后计算阅读时长分钟数状态转回未借STATE_IDLE累计有效借阅次数和总时长这种设计完美解决了题目中的几个陷阱多次借一次还由于重复借阅不会改变状态总是以最后一次借阅时间为准跨天记录通过日期标记自动过滤无效的还书操作无效操作未借时还书、同一天重复还书都会被状态机自动拦截3. 时间计算的实用技巧计算借阅时长时容易出错特别是跨小时的情况。比如08:50借出09:10归还实际应该是20分钟但直接相减会得到错误结果。这里分享一个经过验证的计算公式int get_duration(int borrow_h, int borrow_m, int return_h, int return_m) { // 统一转换为分钟数再计算 int start borrow_h * 60 borrow_m; int end return_h * 60 return_m; return max(0, end - start); // 防止出现负数 }这个方法的优势在于避免复杂的时分进位计算如60分钟进1小时天然支持跨午夜的极端情况虽然题目不要求代码可读性强容易调试实际使用时还需要注意题目要求四舍五入建议单独写个处理函数当有效借阅次数为0时要特殊处理避免除以零错误每天的统计需要清零不能累积到第二天4. 完整代码实现与优化结合状态机思路这是经过多次优化后的核心代码结构#include iostream #include cmath using namespace std; #define STATE_IDLE 0 #define STATE_BORROWED 1 struct Book { int state STATE_IDLE; int day -1; int borrow_h, borrow_m; } books[1001]; int main() { int days; cin days; for (int day 1; day days; ) { int id; char op; int h, m; cin id op; scanf(%d:%d, h, m); if (id 0) { // 当日结束 // 输出统计结果 day; continue; } Book b books[id]; if (op S) { b.state STATE_BORROWED; b.day day; b.borrow_h h; b.borrow_m m; } else if (op E b.state STATE_BORROWED b.day day) { // 有效还书处理 b.state STATE_IDLE; } } return 0; }几个值得注意的优化点使用数组而非map因为题目明确编号范围是1-1000结构体初始化默认值避免忘记初始化日期标记与状态双重校验确保不会误处理跨天记录引用传递(b)减少数组访问开销5. 常见错误与调试技巧在实现过程中我踩过几个典型的坑问题1忽略同一天多次借阅现象测试样例中同一本书被反复借出时计算结果错误原因没有更新最近的借阅时间解决无论当前状态如何遇到S指令都更新借阅时间问题2跨天记录处理不当现象前一天借的书第二天还被错误计入统计原因没有记录借阅发生的具体日期解决在结构体中增加day字段还书时校验日期一致性问题3时间计算错误现象08:50-09:10计算结果不是预期的20分钟原因直接小时相减未考虑分钟进位解决统一转换为分钟数再计算调试时建议准备特殊测试用例同一本书连续借还跨午夜的借还记录无效操作序列如E S E S打印状态转换日志printf(Book %d: %c at %02d:%02d, state%d-%d\n, id, op, h, m, old_state, new_state);使用assert检查不变量assert(books[id].state STATE_IDLE books[id].state STATE_BORROWED);6. 状态机思想的扩展应用这种建模方式不仅适用于阅览室场景还能解决许多类似的时序逻辑问题。比如会议室预约系统状态空闲、已预约、使用中事件预约、开始使用、结束使用、取消预约转换规则类似但需要处理更多边界条件共享单车管理系统状态库存中、已借出、维修中事件扫码借车、还车、报修需要处理GPS定位等额外信息在实际项目中状态机通常用更专业的方式实现使用状态模式State Pattern面向对象实现通过状态转换表State Transition Table配置驱动结合事件队列Event Queue处理异步操作对于算法竞赛而言掌握这种基础的状态机建模思想可以让你在面对复杂的流程控制题时事半功倍。建议读者可以尝试用这个思路解决PTA上的类似题目比如L2-025的银行排队模拟。

更多文章