在算法竞赛(如 CSP、NOIP、蓝桥杯等)中,我们经常遇到这样的情况:样例过了,交上去却得了零分(WA)。肉眼去几百行的代码里找 Bug 犹如大海捞针。
这时候,“对拍”(Pairing Test)就成了程序员的终极核武器。
本文将带你在 Windows 环境下,使用 C++ 编写一个自动化对拍程序,让电脑帮你成千上万次地自我纠错。
一、 什么是“对拍”?
对拍的核心思想非常简单,它需要 4 个核心部分:
1. 数据生成器(gen.cpp):随机生成合法的输入数据。
2. 正解/暴力程序(std.cpp):逻辑绝对正确、时间复杂度可能较慢的暴力程序(或者你确信正确的代码)。
3. 待测程序(my.cpp):你自己写的、可能存在 Bug 的高效算法程序。
4. 对拍控制脚本(run.cpp 或 .bat):指挥它们循环运行、对比结果。
对拍的运行流程:
[数据生成器 gen] ---> 生成输入数据 (data.in)
|
+------------------+------------------+
| |
v v
[待测程序 my] [暴力程序 std]
| |
v v
输出结果 (my.out) 正确答案 (std.ans)
| |
+------------------+------------------+
|
v
[对比工具:一样吗?]
/ \
(是) (否)
/ \
进入下一轮 【抓获 Bug!停下!保留现场】
二、 实战演练:四步打造对拍系统
假设我们现在有一道题目,要求计算 两数之和。
第一步:准备好你的待测程序(my.cpp)
这是你写的代码(假设里面可能藏着 Bug):
#include <iostream>
using namespace std;
int main() {
int a, b;
// 注意:对拍时程序直接用标准输入输出即可,不需要写 freopen!
if (cin >> a >> b) {
cout << a + b << endl;
}
return 0;
}
第二步:写一个暴力正确程序(std.cpp)
这个程序不需要考虑时间复杂度多高,只要能用最稳妥、最笨的方法算出正确答案就行。
#include <iostream>
using namespace std;
int main() {
int a, b;
while (cin >> a >> b) {
cout << a + b << endl;
}
return 0;
}
第三步:写一个随机数据生成器(gen.cpp)
利用 C++ 的 <random> 或 <ctime> 随机生成符合题目数据范围的输入。
#include <bits/stdc++.h>
using namespace std;
int main() {
// 以时间为种子,保证每次生成的随机数不同
srand(time(0));
// 随机生成两个 -1000 到 1000 之间的数字作为 a 和 b
int a = rand() % 2000 - 1000;
int b = rand() % 2000 - 1000;
cout << a << " " << b << endl;
return 0;
}
第四步:编写 C++ 版对拍控制器(run.cpp)
很多教程喜欢用 Windows 的 .bat 批处理文件写对拍,但用 C++ 编写对拍器 跨平台、更好控制、更符合程序员习惯。
新建一个 run.cpp 文件:
#include <bits/stdc++.h>
using namespace std;
int main() {
int k = 1; // 记录对拍到第几轮了
while (true) {
// 1. 编译各个程序(确保你已经把它们编译成了 .exe)
// 如果你用 Dev-C++ 或 VS Code 已经编译好了,这行可以注释掉
// 2. 生成随机数据到 data.in
system("gen.exe > data.in");
// 3. 分别运行你的程序和暴力程序,重定向输入输出
system("my.exe < data.in > my.out");
system("std.exe < data.in > std.ans");
// 4. 对比两个程序的输出结果 (fc 是 Windows 自带的文件比较命令)
// /W 会忽略空格和换行符差异
if (system("fc my.out std.ans > nul") != 0) {
// 如果返回值不为 0,说明结果不一致!发生 WA!
cout << "第 " << k << " 组数据:\n";
cout << "发现不同!程序挂了!\n";
break; // 停止对拍,保留现场
} else {
// 结果一致,输出 AC
cout << "第 " << k << " 组数据:AC\n";
}
k++;
}
return 0;
}
三、 如何在 Windows 下运行它?
- 编译所有文件:
将上面的
my.cpp、std.cpp、gen.cpp、run.cpp分别编译,生成对应的可执行文件: my.exestd.exegen.exe-
run.exe(确保它们四个在同一个文件夹下。) -
双击运行对拍: 直接双击运行
run.exe(或者在该目录下打开命令行窗口运行run.exe)。 -
查看结果:
- 如果程序一直刷
第 x 组数据:AC,说明你的my.cpp在随机数据下表现完美。 - 如果某一轮出现了
发现不同!程序挂了!,循环会停下来。此时文件夹里会多出三个文件:data.in(出错的数据)、my.out(你的输出)、std.ans(正确输出)。 - 你可以直接打开
data.in,把里面的恶心数据复制到本地代码里进行断点调试(Debug),轻松揪出 Bug。
四、 避坑指南
- 程序中千万别写
freopen: 在写my.cpp和std.cpp时,绝对不能加文件输入输出,必须纯靠cin/cout或scanf/printf,因为对拍器的system("my.exe < data.in > my.out")已经帮你接管了重定向。 - 随机数的范围要够大:
用
rand()时注意rand()最大只能到32767。如果题目数据范围到 $10^9$,需要写(rand() << 15) | rand()来拼接生成大随机数。 - 浮点数对拍注意精度:
如果题目输出是浮点数,Windows 的
fc命令对微小误差很敏感,通常需要写一个专门的cmp.cpp来判断两浮点数之差是否小于1e-7。
—— 本文来自火龙信奥(义乌睿码科技):义乌青少年信息学奥赛与编程教育平台,专注 CSP-J/S、NOIP、GESP 竞赛培训,线上线下融合教学,助力编程升学。网址:hlcoding.com