對拍(C++)

EdisonBa發表於2020-08-15

對拍(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份程式碼有了,我們把它放在同一個資料夾裡。這樣算是做好了對拍的準備。

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個隨機數。

​ 注:如果不加那個隨機種子,生成的隨機數每次都是一樣的數。

如果我們有對於資料範圍的要求,那怎麼辦呢?

​ 要讓隨機數限定在一個範圍,可以採用模除加加法的方式。

​ 對於任意數,\(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;
    	}
    }
    

    看一下輸出結果

    怎麼樣,是不是很神奇呢(滑稽)

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 程式。如下:

​ 然後,開啟 duipai.exe ,我們可以看到電腦正在對兩個檔案進行比較

​ 找不到差異,說明這兩份程式碼輸出的兩個檔案是一樣的。

​ 那麼我們可以一直拍著,如果長時間都是找不到差異,那麼你寫的正解就可能是對的了。

​ 如果找到差異,它會分別返回兩個檔案的資料,這樣我們就有了一組錯誤資料,方便我們 debug 。

插入圖片7

​ 這是存在差異的情況。

5.程式的美化

​ 眾所周知,每一道編寫程式題都有時間限制。那麼我們可以用一個計時函式,來計算我們寫的正解用的時間,判斷它是否超時,並把所用時間在對拍程式上體現出來。

​ 我們還可以給把一個通過的資料當作一個測試點,還可以給他賦予編號,這些都能在對拍程式體現出來,像下面這樣:

#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 多少個點來評個總分。(當然這只是起一些美化作用,讓對拍看起來更舒服)

​ 這樣子,是不是感覺很有意思呢(滑稽*2)


總結

​ 經過上面的一番講解,大家一定對“對拍”已經有了一些瞭解。相信大家跟著上面的步驟,也能用對拍來解決一些實際的問題。

​ 在考場上,對於一些 比較容易寫出暴力程式碼 而 寫正解又擔心自己寫不對 的情況,我們可以用自己的暴力程式碼和寫的正解比較一下。(畢竟暴力程式碼肯定不會WA掉,輸出的答案只是慢了些,但答案肯定不會錯) 這麼比較,就知道自己寫的正解是不是對的了。

​ 而且,對拍還能方便地計算出任意隨機資料所跑的時間,我們可以知道這個程式大約用的時間,我們可以自己再去除錯優化。這避免了我們考試時寫完程式碼,但是不知道自己的程式跑大資料非常慢,考試結束交程式評測的時候全是TLE。(悲)

​ 總之,對拍是個比較實用的工具,考試必備神器,一定要掌握它。

希望大家在2020NOIP中發揮超常,RP++!

EdisonBa

2020.8.15

相關文章