对拍(C++)

对拍是什么

​​对拍,是一个比较实用的工具。它能够非常方便地对于两个文件进行比较,可以帮助我们实现一些自动化的问题。

​众所周知,每一道编程题目,都会有某种正解能拿到满分;当我们想不出正解时,我们往往可以打暴力代码来获取部分分数。

​但是,当我们有思路写正解,但又担心自己正解写的不对,而恰好,我们又有一个能够暴力骗分的代码。这个时候就可以用到对拍。 暴力骗分代码必须有正确性,最多只是超时。

​这样,我们可以造几组数据,让暴力骗分代码跑一遍,再让我们自己写的正解跑一遍,二者对比一下。如果拍出来多组数据都显示二者的结果一样,那么这个正解大概率没问题。相反地,如果两组数据不同,我们就找到了一组错误数据,方便调试,找到正解哪里出了问题。

​这便是对拍。其作用也在上文提出。


对拍的实现

1.准备基本代码

​首先,我们要有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 res=0;
	int i;
	for(i=1;i<=a;i++)
		res++;
	for(i=1;i<=b;i++)
		res++;
	printf("%d\n",res);
	return 0;
}

​2份代码有了,我们把它放在同一个文件夹里。这样算是做好了对拍的准备。

C++ 对拍详解-LMLPHP

2.制作数据生成器

​我们制作的数据要求格式和上面两份代码的输入格式一样。
​根据上面,我们可以知道输入的数据为2个数,中间有空格分隔。那么,我们的数据生成器就要输出2个数,中间也要用空格分隔。

#include<cstdio>
#include<cstdlib>
#include<ctime>
int main()
{
	srand(time(0));
    //这是一个生成随机数随机种子,需要用到 ctime 库
	printf("%d %d\n",rand(),rand());
    //这样就生成了2个随机数
	return 0;
}

​运行一下,果然生成了2个随机数。

C++ 对拍详解-LMLPHP

​注:如果不加那个随机种子,生成的随机数每次都是一样的数。

如果我们对于数据范围有要求,那怎么办呢?

​要让随机数限定在一个范围,可以采用模除加加法的方式。

​对于任意数,\(0\leq rand()\%(a+1) \leq a\)

​于是 \(0+k\leq rand()\%(a+1) +k\leq a+k\)

​举几个简单的例子。

  1. a=rand()%2 时,a 的范围:\(0 \leq a \leq 1\)

  2. a=rand()%2+1 时,a 的范围:\(1 \leq a \leq 2\)

  3. 要想让 \(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;
    	}
    }
    

    看一下输出结果

    C++ 对拍详解-LMLPHP

    这种“凑数”的方法是不是感到很神奇呢?

3.对拍代码

​在这里,我们需要用到一些文件的读写符号。(需用到 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。

​那么,我们就可以执行这一操作来实现对拍。

  1. 先让数据生成器输出数据。 system("data.exe > in.txt")
  2. 然后用这个数据跑一遍暴力代码,输出结果。 system("baoli.exe < in.txt > baoli.txt")
  3. 再用这个数据跑一遍你写的正解代码,输出结果。 system("std.exe < in.txt > std.txt")
  4. 把两个结果相比较,判断是不是一样的。 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;
}

4.运行对拍程序

​目前,我们有了4份代码。为了实现对拍,我们要把这些代码放在同一个文件夹的同一层里。

​并且打开每一份代码,让每一份代码都生成一个同名的 .exe 程序。如下:

C++ 对拍详解-LMLPHP

​然后,打开 duipai.exe ,我们可以看到程序正在对两个输出文件进行比较

C++ 对拍详解-LMLPHP

​找不到差异,说明这两份代码输出的两个文件是一样的。

​那么我们可以一直拍着,如果长时间都是找不到差异,那么你写的正解就可能是对的了。

​如果找到差异,它会分别返回两个文件的数据,这样我们就有了一组错误数据,方便我们 debug 。

C++ 对拍详解-LMLPHP

​这是存在差异的情况。

5.程序的优化

​众所周知,每一道编写程序题都有时间限制。那么我们可以用一个计时函数"clock()",来计算我们写的正解用的时间,判断它是否超时,并把所用时间在对拍程序上体现出来。

​我们还可以给把一个通过的数据当作一个测试点,还可以给他赋予编号,这些都能在对拍程序体现出来,像下面这样:

#include<iostream>
#include<cstdio>
#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
		}
    }
    cout<<endl;
    double res=100.0*ok/n;
    printf("共 %d 组测试数据,AC数据 %d 组。 得分%.1lf。",n,ok,res);
}

​上面造了50个测试点,我们还可以计算程序 AC 多少个点来评个总分。这样可以让我们大致地了解一下编出的程序的正确性。

C++ 对拍详解-LMLPHP

C++ 对拍详解-LMLPHP

​这样子,对拍的作用就发挥到了极致。


总结

​经过上面的一番讲解,大家一定对 “对拍” 已经有了一些了解。相信大家跟着上面的步骤,也能用对拍来解决一些实际的问题。

​在考场上,对于一些 比较容易写出暴力代码 而 写正解又担心自己写不对 的情况,我们可以用自己的暴力代码和写的正解比较一下。(毕竟暴力代码肯定不会WA掉,输出的答案只是慢了些,但答案肯定不会错) 这么比较,就知道自己写的正解是不是对的了。

​而且,对拍还能方便地计算出任意随机数据所跑的时间,我们可以知道这个程序大约用的时间,我们可以自己再去调试优化。这避免了我们考试时写完代码,但是不知道自己的程序跑大数据非常慢,考试结束交程序评测的时候全是TLE。(悲)

​总之,对拍是个比较实用的工具,它非常方便地对两个文件进行了比较操作。这是编程的必备神器,大家一定要好好掌握!

希望大家在2020NOIP中发挥超常,RP++!

EdisonBa

2020.8.15

08-16 00:50