C++ 對拍詳解

C++對拍詳解

對拍是什麼

​ 對拍,是一個比較實用的工具。它可以很是方便地對於兩個程序的輸出文件進行比較,能夠幫助咱們實現一些自動化的比較輸出結果的問題。ios

​ 衆所周知,每一道編程題目,都會有某種正解能拿到滿分;當咱們想不出正解時,咱們每每能夠打暴力代碼來獲取部分分數。c++

​ 可是,當咱們以爲有思路寫正解,但又擔憂本身正解寫的不對,而剛好,咱們又有一個可以暴力騙分的代碼。這個時候就能夠用到對拍。 暴力騙分代碼必須保證正確性,最多隻是超時,不能出現答案錯誤的狀況。編程

​ 這樣,咱們能夠造多組數據,讓暴力騙分的程序跑一遍,再讓咱們本身寫的正解跑一遍,兩者進行屢次對比。若是多組數據都顯示兩者的輸出結果同樣,那麼這個正解大機率沒問題。相反地,若是兩組數據不一樣,咱們就找到了一組錯誤數據,方便調試,找到正解哪裏出了問題。windows

​ 這即是對拍。其做用也在上文提出。函數


對拍的實現

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 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個數,中間有空格分隔。那麼,咱們的數據生成器就要輸出2個數,中間也要用空格分隔。

#include<cstdio>
#include<cstdlib>
#include<ctime>
int main()
{
	srand(time(0));
    //這是一個生成隨機數隨機種子,須要用到 ctime 庫
	printf("%d %d\n",rand(),rand());
    //這樣就生成了2個隨機數
	return 0;
}

​ 運行一下,確實生成了2個隨機數。

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

Extra:數據範圍

​ 若是咱們對於數據範圍有要求,那怎麼辦呢?

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

​ 對於任意數,\(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.對拍代碼

①標準輸入輸出代碼

​ 標準輸入輸出指的是:兩份基本代碼和數據生成代碼不含文件輸入輸出操做,如 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。

​ 那麼,咱們就能夠執行這一操做來實現對拍。

  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;
}

②文件輸入輸出

​ 標準輸入輸出指的是:兩份基本代碼和數據生成代碼含有文件輸入輸出操做,如 freopen 等。

​ 由於基本代碼中有文件輸入輸出,因此咱們在對拍代碼中沒必要使用 ' < ' 、' > ' 等符號對文件進行操做。只需運行一下兩個程序,程序會本身輸出文件。

​ 這種文件輸入輸出的模式適合各類大型線下比賽使用。優勢在於對拍的時候不用刪除 freopen 。

  1. 數據生成代碼例子:
#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);
}
  1. 暴力代碼例子:
#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);
}
  1. 正解代碼例子:
#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);
}
  1. 對拍代碼
#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.運行對拍程序

​ 目前,咱們有了4份代碼。爲了實現對拍,咱們要把這些代碼放在同一個文件夾的同一層裏。

​ 而且打開每一份代碼,讓每一份代碼都生成一個同名的 .exe 程序。以下:

​ 而後,打開 duipai.exe ,咱們能夠看到程序正在對兩個輸出文件進行比較

​ 找不到差別,說明這兩份代碼輸出的兩個文件是同樣的。

​ 那麼咱們能夠一直拍着,若是長時間都是找不到差別,那麼你寫的正解就多是對的了。

​ 若是找到差別,它會分別返回兩個文件的數據,這樣咱們就有了一組錯誤數據,方便咱們 debug 。

這是存在差別的狀況。

5.程序的優化

①節約對拍次數

​ 在對拍時,你有沒有發如今 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 重大修改

相關文章
相關標籤/搜索