题目来源:改编自洛谷 P2580 于是他错误的点名开始了。本 Lab 使用课程自定义输入输出协议和独立测试,不复制来源站点的代码或测试。
学习目标
前置知识与环境
先阅读第 9.2 节 散列表的“从关键字到地址”与“链地址法”两节:字符串不是整数键,需要先用多项式滚动散列把它映射成整数,再对表长取模得到桶下标。
在 Lab 目录执行 make doctor 检查环境;未安装 Make 时可在仓库根使用 pnpm 兜底命令(见下文“运行”)。
任务
先给出一串互不相同的名单名字;随后进行 m 次点名。对每次点名输出一行:
- 名字在名单中且首次点到:输出
OK; - 名字在名单中但之前已经点到过:输出
REPEAT; - 名字不在名单中:输出
WRONG。
名字大小写敏感(本 Lab 输入保证全部为小写字母,不做任何大小写转换)。
输入格式
- 第一行:一个整数
n,表示名单人数; - 接下来
n行:每行一个名字,互不相同; - 接下来一行:一个整数
m,表示点名次数; - 接下来
m行:每行一个名字,表示一次点名。
输出格式
共 m 行,每行一个词:OK、REPEAT 或 WRONG,顺序与点名一一对应。
数据范围与限制
| 项目 | 范围 |
|---|---|
名单人数 n | 1 ≤ n ≤ 10⁴ |
点名次数 m | 1 ≤ m ≤ 10⁵ |
| 名字 | 仅小写字母,长度 1 ~ 50;名单内互不相同 |
| 大小写 | 敏感(输入保证小写,不做大小写转换) |
| 时间复杂度要求 | 每次点名期望 |
正常、边界与错误情况
- 正常:三种输出各按定义出现;同一个人第二次及以后被点到都是
REPEAT; - 边界:名单只有 1 个人;同一个人被连续点到多次;点名次数远大于名单人数(m ≫ n);
- 不在名单:名字再常见也一律
WRONG,且不会影响任何状态标记。
样例
样例 1
3
alice
bob
carol
7
alice
david
alice
bob
eve
carol
bobOK
WRONG
REPEAT
OK
WRONG
OK
REPEATalice 首次点到输出 OK,第二次变成 REPEAT;david、eve 不在名单,输出 WRONG;三种状态都出现了。
样例 2
1
tom
3
tom
tom
tomOK
REPEAT
REPEAT名单只有 1 个人;同一个人连点 3 次,只有第一次是 OK。
实现提示
散列表用链地址法:head[表长] 存每条同义词链的头节点下标,节点池数组存 {名字, 是否已点名, next},用下标代替指针做链表。散列函数取 131 进制多项式滚动散列,对素数表长(如 20011)取模;名单规模 1×10⁴ 时装填因子约 0.5。
名单内名字互不相同,插入时不必判重。查询流程:先 find——找不到输出 WRONG;找到再看节点上的标记,未点过则置为已点并输出 OK,点过则输出 REPEAT。用 std::unordered_map<std::string, bool> 做对照可以,但本 Lab 要求提交的手写实现里包含自己的散列函数与冲突处理。
运行
make run
# 免 Make 兜底:在仓库根执行
pnpm lab:run -- labs/chapter-09/exercise/E-09-03-roll-call-status需要单用例或严格评分时使用 make run CASE=005-stress-m1e5 与 make score;也可用 pnpm lab:score -- labs/chapter-09/exercise/E-09-03-roll-call-status 兜底。
完成清单
思考与复盘
- 把“是否已点名”标记挂在链表节点上,与另开一个“名字 → 布尔”的第二张表相比,各有什么代价?
- 如果题目改成大小写不敏感,应该在哪一步做归一化?直接把大写字母转小写再散列,会破坏散列函数的什么性质?
- 当 n = 10⁴、表长取 101 时,链地址法的平均查找长度大约是多少?对 m = 10⁵ 次点名意味着多少次字符串比较?