对拍是一种在OI赛制下很实用的一种调代码的方法,它可以验证代码输出是否正确
注意,本博客针对与Dev-C++用户编写
1.对拍基本设置
首先,对拍需要一个保证正确的暴力程序(比如要求111~nnn所有数字的和,可以用111~nnn的for循环暴力求和,这样保证正确,但是大数据下过不了)、待检验的程序(还是上面的例子,这个程序可以是求和公式n×(n+1)2\frac{n \times (n+1)}{2}2n×(n+1))以及数据生成程序
2.数据生成器
数据生成程序需要用到随机数函数mt19937,他会比srand更好,重复率更低
接下来展示几个常见数据生成函数:
(注:以下代码由Deepseek大模型生成)
//生成整数或小数#include<bits/stdc++.h>usingnamespacestd;// 高品质随机数(避免 Windows 下 rand() 周期短的问题)mt19937rng(chrono::steady_clock::now().time_since_epoch().count());// 返回 [l, r] 的随机整数intrandint(intl,intr){returnuniform_int_distribution<int>(l,r)(rng);}// 返回 [l, r] 的随机小数doubleranddouble(doublel,doubler){returnuniform_real_distribution<double>(l,r)(rng);}intmain(){intn=randint(1,10);//生成1~10的整数intm=randint(1,10);//同上doublek=randdouble(1,2);//生成1~2的小数cout<<n<<" "<<m<<" "<<k;return0;}//用于生成1~n的排列(即1~n各出现一次)#include<bits/stdc++.h>usingnamespacestd;mt19937rng(chrono::steady_clock::now().time_since_epoch().count());intrandint(intl,intr){returnuniform_int_distribution<int>(l,r)(rng);}intmain(){intn=randint(1,10);vector<int>p(n);iota(p.begin(),p.end(),1);shuffle(p.begin(),p.end(),rng);for(inti=0;i<n;i++)cout<<p[i]<<" ";return0;}//生成一颗随机树#include<bits/stdc++.h>usingnamespacestd;mt19937rng(chrono::steady_clock::now().time_since_epoch().count());intrandint(intl,intr){returnuniform_int_distribution<int>(l,r)(rng);}intmain(){intn=randint(1,10);// 方法:每次新点连向已有的随机点(生成随机树)vector<int>parent(n+1);for(inti=2;i<=n;i++){parent[i]=randint(1,i-1);// 保证不形成环}for(inti=2;i<=n;i++){cout<<i<<" "<<parent[i]<<"\n";}return0;}//生成一张随机无向图(含边权)//n个点,m条边,没有重边和自环#include<bits/stdc++.h>usingnamespacestd;mt19937rng(chrono::steady_clock::now().time_since_epoch().count());intrandint(intl,intr){returnuniform_int_distribution<int>(l,r)(rng);}intmain(){intn=randint(1,10);// 随机点数intm=randint(1,15);// 随机边数set<pair<int,int>>edges;// 用set去重,避免生成重复边while((int)edges.size()<m){intu=randint(1,n);intv=randint(1,n);if(u==v)continue;// 不要自环if(u>v)swap(u,v);// 保证 u < v,便于去重edges.insert({u,v});}for(auto[u,v]:edges){intw=randint(1,100);// 随机边权cout<<u<<" "<<v<<" "<<w<<"\n";}return0;}//生成一个只含有小写字母的字符串#include<bits/stdc++.h>usingnamespacestd;mt19937rng(chrono::steady_clock::now().time_since_epoch().count());intrandint(intl,intr){returnuniform_int_distribution<int>(l,r)(rng);}intmain(){string s;for(inti=0;i<n;i++){s+=char('a'+randint(0,25));}cout<<s<<"\n";return0;}//用于生成区间查询(常用于莫队,线段树,前缀和等)#include<bits/stdc++.h>usingnamespacestd;mt19937rng(chrono::steady_clock::now().time_since_epoch().count());intrandint(intl,intr){returnuniform_int_distribution<int>(l,r)(rng);}intmain(){intl=randint(1,n);intr=randint(l,n);// 保证 l <= rcout<<l<<" "<<r<<"\n";return0;}//用于生成多组输入#include<bits/stdc++.h>usingnamespacestd;mt19937rng(chrono::steady_clock::now().time_since_epoch().count());intrandint(intl,intr){returnuniform_int_distribution<int>(l,r)(rng);}intmain(){intT=randint(1,5);//T也要随机数cout<<T<<"\n";while(T--){//生成每组数据}return0;}3.示例
题目大意:输入一个数nnn,输出111~nnn的和
现在桌面上创建一个文件夹,如图:
在这个文件夹里面写上面三个程序,第一个暴力程序,写好后保存为baoli.cpp(名字可以自行更改,但是用这个名字更易于理解),然后编译,得到baoli.exe。再写第二个待检验程序,写好后保存为need.cpp,然后编译,得到need.exe。再写第三个数据生成程序,记为data.cpp,然后编译,得到data.exe
那么暴力代码很简单:
#include<bits/stdc++.h>usingnamespacestd;intmain(){intn;cin>>n;intcnt=0;for(inti=1;i<=n;i++){cnt+=i;}cout<<cnt;return0;}待检验代码就是求和公式:
#include<bits/stdc++.h>usingnamespacestd;intmain(){intn;cin>>n;cout<<(n+1)*n/2;return0;}数据生成程序(生成一个整数nnn即可)
#include<bits/stdc++.h>usingnamespacestd;mt19937rng(chrono::steady_clock::now().time_since_epoch().count());intrandint(intl,intr){returnuniform_int_distribution<int>(l,r)(rng);}intmain(){intn=randint(1,100);cout<<n<<"\n";return0;}好了,现在文件夹应该长这样子:
现在还需要一个脚本程序duipai.bat,它的作用就是自动循环执行「生成数据 → 跑暴力程序 → 跑待检验程序 → 比对结果」这一整套流程,一旦发现两边输出不一致就立刻停下来提醒你。
就在这个文件夹里面新建一个记事本:
重命名为duipai.bat(一定要记得更改扩展名.txt,不要是duipai.bat.txt)
现在右键点击duipai.bat(注意不是双击运行)
打开之后,里面应该是空的
复制下面这一段脚本代码到duipai.bat
@echo off :loop data.exe > input.txt baoli.exe < input.txt > std.txt need.exe < input.txt > my.txt fc std.txt my.txt if not errorlevel 1 goto loop pause goto loop保存,退出,双击运行
此时应该是无法运行的(如果您运行成功了就请跳转到标题4)
因为g++是静态的,而这个脚本需要动态的环境,那怎么办?
找到自己的Dev-C++
右键点击,找到打开文件所在的位置按钮,就可以跳转到一个文件夹Dev-Cpp(不同版本可能不一样)
然后找到MinGW64文件夹,打开(注意不是MinGW32)
找到bin文件夹,打开
在很多文件里面找到libwinpthread-1.dll,复制
粘贴到对拍文件夹,如图
现在双击运行duipai.bat
如果黑色窗口一直在输出,说明对拍始终通过,暂时没有异常
退出黑色窗口,发现多了几个文件input.txtmy.txtstd.txtmy.txt的作用是记录待检验代码的输出,std.txt的作用是记录暴力代码的输出,input.txt的作用将在下文讲
现在把待检验代码加点料
#include<bits/stdc++.h>usingnamespacestd;intmain(){intn;cin>>n;if(n>=95)cout<<"*";cout<<(n+1)*n/2;return0;}再编译,运行duipai.bat
发现他运行到一半停止了,说明发现了错误,此时退出黑色窗口,打开my.txt和std.txt可以查看待检验代码的输出(就是那个加了料的程序)和暴力程序输出,再打开input.txt,就可以查看那个错误的输入
4.注意
1.测试代码必须要和暴力代码输出一模一样,行末空格和末尾回车一定要保持一致,不能一个有一个没有,这是会判错的
2.所有的文件都要放在同一个文件夹下
3.代码文件有一点修改都需要重新编译
4.本方法仅能查出WA,RE等问题,查不出TLE与MLE,请在赛时自行检查时间复杂度和空间复杂度