OI 测试点生成系统
把"读题 → 设计 10 个测试点 → 写 .in/.out → 跑参考程序验证"这一整套流程固化成可复用工具。
何时触发
- 用户给出"题目 PDF + 参考程序",要求出测评数据
- 用户说"按这套题出 N 个测试点 / 帮我准备数据评测学生程序"
- 用户希望把现有题库的旧 a/b/c 编号数据改成"按题分文件夹"的新结构
整体思路
- 理解题意——读 PDF(中文常丢字)或参考程序,反推 I/O 格式、约束、算法
- 设计 10 个测试点——覆盖:样例 / 最小值 / 最大值 / 边界 / 随机 / 退化情形
- 写文件并验证——用参考程序跑出答案,再用一个独立的"暴力法"Python 函数交叉校验
工具构成
| 文件 | 角色 |
|---|---|
scripts/make_test_points.py |
核心库 + CLI:TestPointBuilder 类、python make_test_points.py --config ... |
scripts/example_nhoi2016.py |
完整示例:NHOI 2016 小学甲组 6 道题 |
scripts/problems.example.json |
JSON 配置示例(CLI 模式) |
references/workflow.md |
"如何反推算法 & 设计 10 个测试点"的速查表 |
库模式(推荐)
from make_test_points import TestPointBuilder
builder = TestPointBuilder(output_dir="test_points")
# 同时给参考程序 + 暴力法,工具会用两者交叉验证
builder.add_problem(
name="luck",
reference_cpp="source/参考程序/luck.cpp",
brute_force=lambda s: f"{int(s)//2 + int(s)//3 - int(s)//6}\n",
cases=[
("10\n", "PDF 样例"),
("100\n", "百级"),
("2000000000\n", "上界"),
# …共 10 个
],
)
# 没有 .cpp 时也可以只给 brute_force,靠 Python 函数直接生成答案
builder.add_problem(name="pure_py", brute_force=..., cases=[...])
builder.build() # 编译 → 跑 → 写 .in/.out → 校验
build() 返回 {问题名: {"passed": N, "total": N, ...}};任何一对参考程序 vs 暴力法不一致会立即失败并打印差异。
CLI 模式
python scripts/make_test_points.py --config problems.json
python scripts/make_test_points.py --config problems.json --only luck,run
python scripts/make_test_points.py --config problems.json --output-dir eval/2026
problems.json 形状:
{
"output_dir": "test_points",
"problems": [
{
"name": "luck",
"reference": "source/参考程序/luck.cpp",
"brute_force": "def brute(s):\n n = int(s.strip())\n return f'{n//2 + n//3 - n//6}\\n'",
"cases": [
{"input": "10\n", "label": "PDF 样例"}
]
}
]
}
reference路径相对 JSON 文件所在目录解析brute_force是一段 Python 源码,工具用exec取里面唯一可调用对象- 没有
reference时,只用brute_force生成答案(适合纯解释/小规模题)
自动找编译器
工具按下面顺序找一个能用的 C++ 编译器:
PATH里的g++/clang++C:\Program Files (x86)\Dev-Cpp\MinGW64\bin\g++.exe(南海机房常见)C:\TDM-GCC-64\bin\g++.exeC:\mingw64\bin\g++.exe- Linux/macOS 上的
/usr/bin/g++
找不到会抛 RuntimeError。可以传 compiler=r"..." 显式指定。
实施步骤(给 Agent)
- 读题——优先
pdftotext -layout <pdf> <txt>;中文乱码就转去读参考程序*.cpp/*.pas反推算法 - 看现有数据——多数 NHOI/NOIP 题库已自带
data/<题目名>/aN.in/out等,可先用verify.py思路跑一遍确认它们和参考程序一致 - 设计 10 个测试点——参考
references/workflow.md的覆盖矩阵 - 写一份"暴力法" Python——比参考程序更简单但正确的实现,专门用来交叉验证参考程序的答案
- 调用
TestPointBuilder——传reference_cpp+brute_force+cases - 跑
builder.build()——失败时改 brute 或 cases 后重跑,直到全绿
已知陷阱
- PDF 中文乱码:pdftotext 对嵌入字体经常丢字,必须以参考程序为准反推算法
- 参考程序自带 bug:NHOI 老题库偶尔有;
brute_force就是为此设的护栏 - Dev-Cpp TDM 4.9.2 旧:支持 C++11,复杂的 C++17 语法会编译失败;可换
compiler指向更新的 g++ - 大文件:默认
timeout=60;长题可改subprocess.run(..., timeout=...) - 输出文件用 CRLF vs LF:用
newline=""打开,保证 .in / .out 跟本地评测一致
验证本次(2026-10-14)
NHOI 2016 小学甲组 6 道题 × 10 测试点 = 60 个 .in/.out 全部生成并通过:
run : OK (1.in: 174 73 6 → 24)
luck : OK (1.in: 10 → 7)
rank : OK (1.in: 225 vs 235 → 2)
cow : OK (1.in: 11 0 + 11001111011 → 4)
divisor : OK (1.in: PDF 16 查询样例)
del : OK (1.in: 1256 → 4)
直接重跑:python scripts/example_nhoi2016.py