對拍,是一個比較實用的工具。它可以很是方便地對於兩個程序的輸出文件進行比較,能夠幫助咱們實現一些自動化的比較輸出結果的問題。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 重大修改