对拍,是一个比较实用的工具。它可以很是方便地对于两个程序的输出文件进行比较,能够帮助咱们实现一些自动化的比较输出结果的问题。ios
众所周知,每一道编程题目,都会有某种正解能拿到满分;当咱们想不出正解时,咱们每每能够打暴力代码来获取部分分数。c++
可是,当咱们以为有思路写正解,但又担忧本身正解写的不对,而刚好,咱们又有一个可以暴力骗分的代码。这个时候就能够用到对拍。 暴力骗分代码必须保证正确性,最多只是超时,不能出现答案错误的状况。编程
这样,咱们能够造多组数据,让暴力骗分的程序跑一遍,再让咱们本身写的正解跑一遍,两者进行屡次对比。若是多组数据都显示两者的输出结果同样,那么这个正解大几率没问题。相反地,若是两组数据不一样,咱们就找到了一组错误数据,方便调试,找到正解哪里出了问题。windows
这即是对拍。其做用也在上文提出。函数
首先,咱们要有2份代码,一份是这一道题“你写的正解”代码,另外一份是同一道题“你打的暴力”代码。工具
为了方便,咱们先用 A+B problem 来演示对拍。测试
本身的代码: std.cpp大数据
#include<cstdio> using namespace std; int main() { int a,b; scanf("%d%d",&a,&b); printf("%d\n",a+b); return 0; }
暴力代码:baoli.cpp优化
#include<cstdio> using namespace std; int main() { int a,b; scanf("%d%d",&a,&b); int ans=0; int i; for(i=1;i<=a;i++) ans++; for(i=1;i<=b;i++) ans++; printf("%d\n",ans); return 0; }
2份代码有了,咱们把它放在同一个文件夹里。这样算是作好了对拍的准备。ui
咱们制做的数据要求格式和上面两份代码的输入格式同样。
根据上面,咱们能够知道输入的数据为2个数,中间有空格分隔。那么,咱们的数据生成器就要输出2个数,中间也要用空格分隔。
#include<cstdio> #include<cstdlib> #include<ctime> int main() { srand(time(0)); //这是一个生成随机数随机种子,须要用到 ctime 库 printf("%d %d\n",rand(),rand()); //这样就生成了2个随机数 return 0; }
运行一下,确实生成了2个随机数。
注:若是不加那个随机种子,生成的随机数每次都是同样的数。
若是咱们对于数据范围有要求,那怎么办呢?
要让随机数限定在一个范围,能够采用 “模除加加法” 的方式。
对于任意数,\(0\leq rand()\%(a+1) \leq a\) 。
因而 \(0+k\leq rand()\%(a+1) +k\leq a+k\) 。
举几个简单的例子:
当 a=rand()%2
时,a 的范围:\(0 \leq a \leq 1\) 。
当 a=rand()%2+1
时,a 的范围:\(1 \leq a \leq 2\) 。
要想让 \(1 \leq a \leq 30000\) ,则 a=rand()%30000+1
。
可是,这里有个小问题。rand() 生成的随机数的范围在0~32767之间。若是咱们想要获得比32767更大的随机数怎么办呢?我有一个小办法,很实用。
好比让 \(1 \leq a \leq 1,000,000\)
int main() { srand(time(0)); while(1) { int a=rand()%1000+1; int b=rand()%990+1; // a的最大值 × b的最大值=990000 int c=rand()%10000+1; //a*b+c 恰好凑个1000000 int d=a*b+c; cout<<d<<endl; } }
看一下输出结果
这种“凑数”的方法是否是感到很神奇呢?
标准输入输出指的是:两份基本代码和数据生成代码里不含文件输入输出操做,如 freopen 等。
在这里,咱们须要用到一些文件的读写符号。(需用到 <cstdlib> 库)
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 ,若是两个文件里的数据相同返回0,不一样返回1。
那么,咱们就能够执行这一操做来实现对拍。
system("data.exe > in.txt")
system("baoli.exe < in.txt > baoli.txt")
system("std.exe < in.txt > std.txt")
system("fc std.txt baoli.txt")
#include<cstdio> #include<cstdlib> #include<ctime> using namespace std; int main() { while(1) //一直循环,直到找到不同的数据 { system("data.exe > in.txt"); system("baoli.exe < in.txt > baoli.txt"); system("std.exe < in.txt > std.txt"); if(system("fc std.txt baoli.txt")) //当 fc 返回1时,说明这时数据不同 break; //不同就跳出循环 } return 0; }
标准输入输出指的是:两份基本代码和数据生成代码里含有文件输入输出操做,如 freopen 等。
由于基本代码中有文件输入输出,因此咱们在对拍代码中没必要使用 ' < ' 、' > ' 等符号对文件进行操做。只需运行一下两个程序,程序会本身输出文件。
这种文件输入输出的模式适合各类大型线下比赛使用。优势在于对拍的时候不用删除 freopen 。
#include<bits/stdc++.h> int main() { srand(time(0)); freopen("in.in","w",stdout); //生成 使两份基本代码 将要读入的数据 int a=rand(),b=rand(); printf("%d %d\n",a,b); }
#include<bits/stdc++.h> int main() { freopen("in.in","r",stdin);//读入数据生成器造出来的数据 freopen("baoli.txt","w",stdout);//输出答案 int a,b,ans=0; scanf("%d %d",&a,&b); for(int i=1;i<=a;++i) ans++; for(int i=1;i<=b;++i) ans++; printf("%d\n",ans); }
#include<bits/stdc++.h> int main() { freopen("in.in","r",stdin); freopen("std.txt","w",stdout); scanf("%d %d",&a,&b); printf("%d\n",a+b); }
#include<cstdio> #include<cstdlib> #include<ctime> using namespace std; int main() { while(1) //一直循环,直到找到不同的数据 { system("data.exe"); system("baoli.exe"); system("std.exe"); if(system("fc std.txt baoli.txt")) //当 fc 返回1时,说明这时数据不同 break; //不同就跳出循环 } return 0; }
目前,咱们有了4份代码。为了实现对拍,咱们要把这些代码放在同一个文件夹的同一层里。
而且打开每一份代码,让每一份代码都生成一个同名的 .exe 程序。以下:
而后,打开 duipai.exe ,咱们能够看到程序正在对两个输出文件进行比较
找不到差别,说明这两份代码输出的两个文件是同样的。
那么咱们能够一直拍着,若是长时间都是找不到差别,那么你写的正解就多是对的了。
若是找到差别,它会分别返回两个文件的数据,这样咱们就有了一组错误数据,方便咱们 debug 。
这是存在差别的状况。
在对拍时,你有没有发如今 cmd 的黑色框框里面,“找不到差别” 这几行输出的很快,看起来对拍的频率好像很高的样子。实际上,这样浪费了不少次对拍,数据生成须要必定的时间,而文件的读取输出等都须要必定时间。可是两个输出文件的对比却在不停地运行着,数据生成器生成的文件在必定的时间内是相同的,这样就浪费了许屡次对拍。
为此,咱们可使每次对拍完毕后休眠1秒,给四个程序留给必定的缓冲时间,使得每次对拍时,数据生成器生成的数据都不一样。
那么,咱们可使用 <windows.h> 库里的 Sleep(t)
,\(t\) 为时间,单位是毫秒。它可使程序休眠 \(t\) 毫秒。咱们能够在每次对拍以后加上 Sleep(1000)
,这样每次对拍以后休眠1秒,就不会出现浪费对拍的状况了。详见下面代码部分。
众所周知,每一道编写程序题都有时间限制。那么咱们能够用一个计时函数"clock()",来计算咱们写的正解用的时间,判断它是否超时(固然,本地测出的时间和评测机测的时间通常不一样),并把所用时间在对拍程序上体现出来。
咱们还能够给把一个经过的数据看成一个测试点,还能够给他赋予编号,这些都能在对拍程序直观地体现出来,像下面这样:
#include<iostream> #include<cstdio> #include<windows.h> #include<cstdlib> #include<ctime> using namespace std; int main() { int ok=0; int n=50; for(int i=1; i<=n; ++i) { system("make.exe > make.txt"); system("std.exe < make.txt > std.txt"); double begin=clock(); system("baoli.exe < make.txt > baoli.txt"); double end=clock(); double t=(end-begin); if(system("fc std.txt baoli.txt")) { printf("测试点#%d Wrong Answer\n",i); } else if(t>1000) //1秒 { printf("测试点#%d Time Limited Enough 用时 %.0lfms\n",i,t); } else { printf("测试点#%d Accepted 用时%.0lfms\n",i,t); ok++; //AC数量+1 } } pritf("\n"); double res=100.0*ok/n; printf("共 %d 组测试数据,AC数据 %d 组。 得分%.1lf。",n,ok,res); Sleep(1000);//休眠1秒,为了节约对拍次数。 }
上面造了50个测试点,咱们还能够计算程序 AC 多少个点来评个总分。这样可让咱们大体地了解一下编出的程序的正确性。
这样子,对拍的做用就发挥到了极致。
通过上面的一番讲解,你们必定对 “对拍” 已经有了一些了解。相信你们跟着上面的步骤,也能用对拍来解决一些实际的问题。
在考场上,对于一些 比较容易写出暴力代码 而 写正解又担忧本身写不对 的状况,咱们能够用本身的暴力代码和写的正解比较一下。(毕竟暴力代码确定不会WA掉,输出的答案只是慢了些,但答案确定不会错) 这么比较,就能够检查出本身写的正解有没有大问题。
并且,对拍还能方便地计算出任意随机数据所跑的时间,咱们能够知道这个程序大约用的时间,咱们能够本身再去调试优化。这避免了咱们考试时写完代码,可是不知道本身的程序跑大数据很是慢,考试结束交程序评测的时候全是TLE。(悲)
可是,对拍仅仅能确保本身写的正解能跑过一些比较小的数据。若是数据范围太大,一是暴力的程序跑不出来,二是数据生成的程序须要承受更多的压力。因此,若是想要确保能过大数据,须要本身手动去看一下代码里面是否隐藏着问题,好比中间过程要强转为 long long 等等。
总之,对拍是个比较实用的工具,它很是方便地对两个文件进行了比较操做。这是编程的必备神器,你们必定要好好掌握!
但愿你们在2020NOIP中发挥超常,RP++!
EdisonBa
2020.8.15 首次发布
2020.11.4 重大修改