对拍
对拍是什么
对拍,是一个比较实用的工具,可以用于比较两个文件的输出结果,进而验证自己的代码正确性。
有的时候,你在做一道题,通过了样例,交上却WA了;
有的时候,你有正解思路,却不知道自己打的对不对;
这时候,如果你手头有一份保证正确的暴力代码或题解,那么我们可以自己造数据来测试,让暴力先跑一遍,自己打的正解再跑一遍,然后对比答案;若测试了很多组数据答案都相同,那这个正解大概率没问题;反之,我们就得到了一组测试数据共我们调试。
这,就是对拍。
对拍的实现
1.代码准备
我们要有两份代码,一份“完全正确的暴力或题解”,另一份“你写的正解”;
以下以 A + B 为例进行解释:
正解(待测试)代码:待测试.cpp
#include<iostream>
using namespace std;
int main(){
int a,b;
cin>>a>>b;
if(a<=100&&b<=100)cout<<a+b;
else cout<<"我不会,长大以后再来学习";
return 0;
}
暴力代码:暴力.cpp
#include<iostream>
using namespace std;
int main(){
int a,b;
cin>>a>>b;
int ans=0;
for(int i=1;i<=a;i++){
ans++;
}
for(int j=1;j<=b;j++){
ans++;
}
cout<<ans;
return 0;
}
两份代码有了后,将它们全部编译,并放在同一文件夹下;
这算是做好了对拍的准备。
2.数据生成器
那么显然,我们不能自己手搓上万组数据;
还好,c++自带一个函数rand()
,储存在<cstdlib>
头文件中,用于生成随机数。
于是我们得到代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
int a=rand(),b=rand();
cout<<a<<" "<<b;
return 0;
}
接下来你就会发现,连续运行几次程序,数据都是相同的;
原因在于,rand( )使用的是一个固定的种子,每次运行输出的都是由这个种子产生的序列,所以我们要手动更改种子;
即:srand(time(0))
,这句会应用当前时间作为种子,做到一定程度上的真正随机;
但time(0)获得的时间以秒为单位,若在短时间内连续获取,则会浪费大量对拍次数;
所以我们引入_timeb生成毫秒级别:
struct _timeb T;
_ftime(&T);
srand(T.millitm);
这样就可以了。
对于生成随机数的范围,我们可以采取以下方法来生成一个a到b的随机数:
rand()%(b-a+1)+a
或者直接宏定义:#define rand(a,b) (rand()%((b)-(a)+1)+(a))
;
这样,我们就有了一套随机数据生成器:
#include<bits/stdc++.h>
#define rand(a,b) (rand()%((b)-(a)+1)+(a))
using namespace std;
int main(){
struct _timeb T;
_ftime(&T);
srand(T.millitm);
// srand(time(0));上面这个是毫秒级的定种,下面这个也行
int a=rand(1,200),b=rand(1,200);
cout<<a<<" "<<b;
return 0;
}
拍子
重点来了!
一般分两种对拍:
标准输入输出
优点:不用打 freopen 等文件输入输出指令
会用到以下三个指令:
system("A.exe > A.txt")//运行 A.exe,把结果输出(>)到 A.txt 中
system("B.exe < A.txt > C.txt")//运行 B.exe,从 A.txt 中读入(<)数据,把结果输出(>)到 C.txt 中
system("fc A.txt B.txt")//比较 A.txt 和 B.txt
所以:
#include<bits/stdc++.h>
using namespace std;
int main(){
while(1){
system("测试点生成器.exe > test.txt");
system("暴力.exe < test.txt > 暴力.txt");
system("待测试.exe < test.txt > 待测试.txt");
if(system("fc 暴力.txt 待测试.txt"))break;
}
return 0;
}
程序结束后,test.txt 中存的就是样例,暴力.txt是答案,待测试.txt是你的程序输出的错误答案;
文件输入输出
优点:不用删 freopen 等文件输入输出指令
简单地讲,就是把文件的打开,读写的操作分别打到测试点生成器,暴力,待测试正解中。
暴力
#include<iostream>
using namespace std;
int main(){
freopen("test.txt","r",stdin);
freopen("暴力.txt","w",stdout);
int a,b;
cin>>a>>b;
int ans=0;
for(int i=1;i<=a;i++){
ans++;
}
for(int j=1;j<=b;j++){
ans++;
}
cout<<ans;
return 0;
}
待测试正解
#include<iostream>
using namespace std;
int main(){
freopen("test.txt","r",stdin);
freopen("待测试.txt","w",stdout);
int a,b;
cin>>a>>b;
if(a<=100&&b<=100)cout<<a+b;
else cout<<"我不会,长大以后再来学习";
return 0;
}
测试点生成器
#include<bits/stdc++.h>
#define rand(a,b) rand()%((b)-(a)+1)+(a)
using namespace std;
int main(){
freopen("test.txt","w",stdout);
struct _timeb T;
_ftime(&T);
srand(T.millitm);
int a=rand(1,200),b=rand(1,200);
cout<<a<<" "<<b;
return 0;
}
对拍代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
while(1){
system("测试点生成器.exe");
system("暴力.exe");
system("待测试.exe");
if(system("fc 暴力.txt 待测试.txt"))break;
}
return 0;
}
运行对拍程序
-
将四份文件放于同一文件夹下
-
运行对拍程序:
这样就是没问题;
如果找不到差错,那它就会一直拍着;
这样就是出错了。 -
此时的test.txt中存的就是样例:
而“暴力”中就是正确答案,“待测试”中是错误解答:
美化对拍
我们可以给对拍加一些花样,让它能体现出时间,算出分数,等等:
//对拍程序
#include<bits/stdc++.h>
#include<windows.h>
using namespace std;
double be,ed,t;
int ok;
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
system("测试点生成器.exe");
be=clock();
system("待测试.exe"); //你要测试的文件 输出至“待测试.txt”
ed=clock();
t=(ed-be);
system("暴力.exe");//你保证对的暴力或题解 输出至“暴力.txt”
if(system("fc 待测试.txt 暴力.txt")){
printf("测试点%d WA\n\n",i);
system("pause");//错解出现时暂停,酌情打开
}
else if(t>1000){
printf("测试点%d TLE 总用时%.0lfms\n\n",i,t);
system("pause");//超时时暂停,酌情打开
}
else{
printf("测试点%d AC 总用时%.0lfms\n\n",i,t);
ok++;
}
}
double res=100.0*ok/n;
printf("\n共%d组数据,AC数据%d组,得分%.1lf",n,ok,res);
return 0;
}
总结
相信经过上面的过程,大家对“对拍”也有了一定的了解,也能用其解决一些实际问题;
在考场上,对于那些比较容易打出暴力的题,我们就很容易用对拍检验自己代码的正确性;而对拍也能算出程序的用时,以防结束提交评测时全TLE;
但对拍也有一定局限,如对于一些大数据,暴力可能所需时间过长,自己的程序也要承受更大压力;所以,要想过大数据,最好还是不要依赖对拍,自己找出代码中的错误,如是否忘开long long等;
总之,对拍是个非常实用的工具,是查错的神器,一定要掌握!
最后,祝所有HE考生们2024省选发挥超常,RP++!