资料说明:原记录中提及的本机脚本及题目附件未随本文附上,相关文件名仅用于说明当时的复现流程。
因果拼图 Causal Mosaic · 三份残缺日志拼一个全序
1 | flag{W3nxZB8RVy4tcddDVAvpHIo8} |
环境:附件 20261015-v5,本机网关 http://127.0.0.1:28911
复现:py -3.13 solve.py(或 python client_example.py --base http://127.0.0.1:28911)
一句话
平台给你三份部分有序的观测(A 直接给前后对、B 给带箭头的字符串、C 给下标对),要你合成一个全序(事件数组);三份答案 A/B/C 必须完全一致地提交同一份结果。本质是“从三个不同编码的偏序关系恢复拓扑序”,难点不在算法,而在看懂三种格式各自的箭头方向。
0. 协议
网页或客户端都只有两步:
1 | POST /api/new -> {"session_id":..., "question_id":..., "challenge":{...}} |
会话 15 分钟、最多 64 次提交;正文上限 256 KiB(三份答案都要塞进去)。答对一次即完成。
1. 三种 traces 的解码(这题的坑全在这)
challenge 里有 events(每个带 id 和 weight)和 traces 三段:
| 来源 | 结构 | 怎么读 |
|---|---|---|
traces.A |
{"before": X, "after": Y} |
直接就是 X → Y |
traces.B |
{"relation": "L <- R"} |
箭头右边先发生,所以是 R → L(反着读!) |
traces.C |
{"pair": [i, j]} |
下标 → 事件 id,即 ids[i] → ids[j] |
1 | for row in traces.get("A", []): |
B 那段是唯一的语义陷阱:字符串看着像 后 <- 前,很多人第一反应按 left → right 连边,结果拼出来的序会让后面的边全部成环。
2. 拓扑排序(Kahn)
1 | indeg = {i: 0 for i in ids} |
len(order) != len(ids) 就是“边读反了/漏了一段”的报警器 —— 这题里它比任何调试输出都好用。
3. 提交与评分
三份答案放同一个数组(平台要求 A/B/C 是同一份完整答案):
1 | post(base, "/api/submit", {"session_id": sid, "question_id": qid, |
每个执行器独立校验,返回 valid/value/digest。本题结果:
1 | "outputs": [ |
value 是事件权重之和(本题 234),digest 是把事件 id 排序后做 SHA-256 —— 也就是说校验的是“序”和“集合”两件事,顺序错了 value 不会变但 valid 会 false。
4. 踩坑
| 坑 | 现象 | 解决 |
|---|---|---|
| B 的箭头方向 | 拼出来成环 / 拓扑长度不足 | "L <- R" 读作 R → L |
| C 用事件 id 当边 | unknown event in edge |
pair 是下标,要过一层 ids[i] |
| 事件集合不完整 | len(order) != len(ids) |
三份 traces 必须全吃进去 |
| 提交非数组 | invalid inputs |
因果拼图提交的是事件 id 数组,不是对象 |
| 会话超时 | session expired |
15 分钟窗口,重新 /api/new 即可(每题答案结构不变) |
5. 一句话总结
这题不难,难在“三种观测格式三种箭头约定”。先统一成有向边,再让算法替你想 —— 只要 len(order) == len(ids),答案就一定是自洽的。
本文作者 Allureluoli
文字版权归作者所有,转载请注明出处;商业用途请先联系二月。