# 实验四 · 一次完整的赛前模拟

> 第 31 课的实验。**这一课的实验本身就是考核**：不是填空、不是补全一个小函数，
> 而是「限时 240 分钟，自己决定做哪几道、放弃哪几道」。判的是**决策与表述**。

---

## 一、这一场考什么

五道省赛难度的题，满分 **360 分**，总时长 **240 分钟**。

**关键前提：五道题全部做完要 470 分钟**（下面那张估时表加起来），
**而考试只有 240 分钟**。所以这一场从第一分钟起就不是「做完五道题」，
而是「在 240 分钟里决定做哪几道、放弃哪几道」。

课件里那 2000 场模拟的结论就是为这一场准备的：

- 1243 / 2000 场里存在「单题耗时超过全场总时长」的题；
- 先啃这种题的人，平均少拿 **156.4** 分；
- 「先难后易」的平均分只有 **47.6**，而「按性价比」是 **133.9**；
- 考前留 30 分钟自检，能多拿 **17～33** 分。

---

## 二、五道题的定位（哪道该做、哪道该放）

| 题 | 题目 | 算法族 | 来源考点 | 分值 | 估时 | 性价比 | 定位 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| p1 | 舞台高度 | 二分答案 | 第 22 课「二分答案」 | 30 | 35 | 0.86 | **入门题**：第 16–26 课的标准算法，必须拿满 |
| p2 | 四边形合并 | 区间 DP | 第 26 课「序列与区间 DP」 | 60 | 70 | 0.86 | **标准题**：模板熟悉，值得做 |
| p3 | 数颜色 | 离线 + 树状数组 | 第 16 课（哈希/离散化）+ 第 28 课（识别套路） | 90 | 110 | 0.82 | **主力题**：性价比高，但要写两段代码 |
| p4 | 最长公共子串 | 二分 + 字符串哈希（或后缀结构） | 第 16 课（哈希）+ 第 22 课（二分） | 100 | **180** | **0.56** | **故意做不完的超难题** |
| p5 | 雨天迷宫 | 0-1 BFS | 第 21 课「DFS / BFS 搜索」 | 80 | 75 | **1.07** | **性价比最高**：模板题，应该先做 |

**p1、p2、p5 都是第 16～26 课里练过的标准算法**（二分答案、区间 DP、BFS），
p3 是它们与第 28 课「识别套路」的组合。**p4 是故意设计成 4 小时做不完的那一道**：

- $|S| = |T| = 10^5$，教科书式的二维 DP 要 $10^{10}$ 个状态（**100 亿个**，
  一个状态 4 字节也要 40 GB），从读题起就知道这条路不通；
- 正解（二分答案 + 双模数哈希）思路本身不长，但要从零写对一条链，
  加上自己验证，**通常要一个多小时**，并且哈希冲突、二分边界、双端点
  三处任何一处出问题，返工都要 30 分钟起；
- 所以它的**正确玩法是「算过的放弃」**：在记录里写清
  「p4 预计 180 分钟 > 剩余 XX 分钟」这样的**时间预算 + 数字**，
  然后去做别的题。**放弃得对，比硬做多拿分。**

> 参考解那一侧（`solutions/`）给出的实战记录是：按性价比做完 p1、p5、p2
> （共 145 分钟，拿满 170 分），然后 p3 没写完、p4 主动放弃，
> 总成绩 **170 / 360**——这一场里「放弃 p4」是对的，
> 「把 p3 排得太靠后」才是真正的失分点。复盘那一节把这件事算清了。

---

## 三、要交的东西

| 交什么 | 交到哪 | 判什么 |
| --- | --- | --- |
| 五道题的答案 | `answers/p1.py` … `answers/p5.py` | `py score.py` 跑测试数据算分 |
| 限时解题记录 | `timeline.md` | `py check_timeline.py` |
| 讲解稿 | `explain.md` | `py check_timeline.py` |

**记录的六格**（每题都要填，没做出来的题也要填）：
读题用时 / 定下的算法（**要说数据范围**）/ 写完时刻 / 自检做了什么 / 得分 / 丢分丢在哪。

**讲解稿的四段**（每题都要写）：
解决什么问题 / 为什么正确（**不变式**）/ 复杂度怎么推 /
**多大的规模会淘汰它（必须带具体数字）**。

> 第四段是这一课真正要判的东西：说不出「多大的规模会淘汰它」，
> 说明你只是记住了这道题，没有把它变成自己的判断力。

---

## 四、怎么跑

```text
py score.py            # 跑 answers/p1.py … p5.py，逐组对比输出，算分（写 score.json）
py check_timeline.py   # 查 timeline.md 与 explain.md：五题齐不齐、放弃有没有依据、四段有没有数字
py run_tests.py        # 判分器自检：检查这一套脚手架本身对不对
```

`run_tests.py` 是**给判分这套工具自己做的体检**（七层）：
校验数据是现算的、参考解真的都对、判分脚本抓得住写错的答案、
流程自检器抓得住写坏的材料、留白版里没有泄漏参考解。
学生平时不用跑它；它是给维护这一课的人用的。

顺手记一下正常的样子：**留白版这一侧跑出「共 65 条 PASS、0 条 FAIL」**（退出码 1，
因为另有 21 条「材料还没填」单独列出），**参考解那一侧是「共 62 条 PASS、0 条 FAIL」**
（退出码 0）。两侧条数不同是正常的——留白版没有 `reference/`，有两层按「查不到」走另一条分支。

**这一侧没有参考解。** 参考解在 `../solutions/0031-lab-04-spaced-repetition-drill/reference/`，
连同填好的 `timeline.md` 与 `explain.md`。先自己写，写完了再去对。

---

## 五、目录里有什么

```text
0031-lab-04-spaced-repetition-drill/
├── README.md               ← 你正在看的这一份
├── timeline_template.md    ← 记录模板（空白份）
├── timeline.md             ← 你要填的记录（现在是空的，等你写）
├── explain_template.md     ← 讲解稿模板（空白份）
├── explain.md              ← 你要填的讲解稿（现在是空的，等你写）
├── answers/                ← 五道题的答案骨架，写在这里
│   └── p1.py … p5.py
├── problems/               ← 五道题（题面 + 样例 + 测试数据）
│   ├── manifest.json       ← 每题的分值与时限
│   ├── p1/                 ← statement.md、samples/、cases/
│   └── p2/ p3/ p4/ p5/
├── score.py                ← 判分：跑答案、比输出、算分
├── check_timeline.py       ← 流程自检：查记录与讲解稿
├── test_data.py            ← 测试数据生成器（固定种子，现算）
└── run_tests.py            ← 判分器自检（维护用）
```

**测试数据是现算的**：`test_data.py` 用固定种子生成全部 30 组数据，
`problems/pN/cases/*.in` / `*.out` 就是算出来的那一份。
每题 6 组，有小的边界组，也有到上限的大数据组（p3 的最大一组是
$n = q = 2 \times 10^5$，p5 的是 $600 \times 600 = 36$ 万格）。

**跑起来要多久**：`run_tests.py` 一份大约 **21 秒**（留白版）/ **27 秒**（参考解那侧），
`score.py` 跑完五道题约 **8 秒**，`check_timeline.py` 不到 1 秒。
`run_tests.py` 是给维护者用的自检（它自己会把参考解、错答案、坏材料都跑一遍），
**你做这一课的时候不需要跑它**。

---

## 六、做完这一场之后

这份记录**不是成绩单，是关于你自己的数据**。复盘那一节要回答：

1. 我的**估时**准不准？哪一类题估得偏乐观、哪一类偏保守？
2. 我的**顺序**排对了吗？如果重排一次，能多拿几分？
3. 我**放弃**的那道，放弃的时刻对吗？再早一点或再晚一点会怎样？
4. 我的**自检**够不够？如果那 30 分钟真的用来自检，能多拿几分？

带着这份数据去第 33 课的综合实战。
