ACM/OI 對拍程式的寫法
搞程式設計競賽的同學很多時候都會因為WA但苦苦找不到錯誤資料而苦惱,雖然肉眼debug的能力也很重要,但有的時候一直手打資料測試兩三天也沒有必要。這裡就介紹一種對拍程式的寫法,是我改進過的,自認為效率應該是比較高了。
如果你懶得學實現細節了,想直接使用,那麼下面的內容可以略過了,到這去下載打包好的,裡面有使用教程:
http://pan.baidu.com/s/1boERyGZ
實現細節
首先對拍程式,顧名思義,一個輸入給兩個程式分別跑一遍,看看對不對的上。
那麼牽扯到三個步驟:
- 生成一組輸入資料
- 把這組資料分別給兩個程式執行,並生成兩組輸出資料
- 比較兩組輸出資料
看到這個步驟很多人應該已經有想法了,沒錯用檔案操作能實現,但太麻煩,因為你得修改你自己的程式碼把輸出重定向到一個檔案,這要是一不小心忘記刪重定向直接交了又得WA一遍233(不對,是RE),這裡介紹一種更方便更高逼格的批處理命令
首先新建一個批處理檔案,命名為簡易對拍程式.bat
,什麼你不知道怎麼新建?右鍵新建一個文字文件直接把字尾名改成bat就好啦,因為批處理檔案本質上就是一堆命令文字嘛。
然後右鍵—編輯,開始打程式碼:
首先第一步:生成一組輸入資料。
我們假設你已經寫好了一個數據生成器,編譯成rand.exe並放在當前目錄下了,那麼我們只要把這個程式的輸入重定向到一個檔案就行了,如果你直接在原始碼裡操作,還得各種檔案流重定向煩得要死。在批處理命令裡很簡單,就一句話:
rand.exe > in.txt
是不是很簡單明瞭?
那怎麼把檔案輸入到一個程式裡去呢?沒錯:
my.exe < in .txt
std.exe < in.txt
my.exe是你寫的錯誤程式,std.exe是標程
那怎麼把兩個程式的輸出再重定向到檔案裡去呢?也很簡單:
my.exe < in.txt > myout.txt
std.exe < in.txt > stdout.txt
是不是相當方便?
接下來就是比較myout.txt和stdout.txt了,也不用你手寫判斷程式,windows自帶一個比較命令:fc(file compare)
fc myout.txt stdout.txt
如果兩個沒有差異,會顯示:找不到差異,否則會顯示不同的附近的幾行的文字。
彙總一下:
rand.exe > in.txt
my.exe < in.txt > myout.txt
std.exe < in.txt > stdout.txt
fc myout.txt stdout.txt
好,這樣一個簡易版對拍程式就寫好了。但這個功能也太簡陋了,只能對拍一次,要是資料難找點豈不是要你執行到手痠?
有人就問了,能不能迴圈?答案是:可以!
@echo off
:loop
rand.exe > in.txt
my.exe < in.txt > myout.txt
std.exe < in.txt > stdout.txt
fc myout.txt stdout.txt
if not errorlevel 1 goto loop
pause
goto loop
別懵逼,一行行給你解釋。
首先@echo off
是關掉輸入顯示,不然你的所有命令都會顯示出來的,防止刷屏。
:loop
是定位標記點,和c語言裡的goto
很像。
中間是主體程式。
if not errorlevel 1 goto loop
,errorlevel
是上一個命令的返回值,fc
在檔案不同時返回1,相同時返回0,這一行的意思就是,如果fc返回的不是1,就跳到:loop
,使勁迴圈。
pause
,暫停,一旦fc
返回1,就會執行到這一行,停住程式,給你時間看資料。
goto loop
,看完資料,按下任意鍵結束暫停,繼續迴圈。
這樣一來功能就頓時強大起來了,為了紀念這麼偉大的改進,我們把檔名重新命名為普通版對拍程式.bat
。(網上流傳的也大多就這個版本了)
但這還不夠! 為什麼? 我們看一下rand程式的寫法。
例如題目格式是,T組資料,每組資料一個n,一個m,然後n個1~m的整數
你就這麼寫:
#include<bits/stdc++.h>
using namespace std;
#define random(a,b) ((a)+rand()%((b)-(a)+1))
int main( int argc, char *argv[] )
{
int seed=time(NULL);
srand(seed);
printf("1\n");
int n=10;
int m=random(1,20);
printf("%d %d\n",n,m);
for(int i=0 ; i<n ; ++i)
{
printf(" %d ",random(0,m));
}
printf("\n");
return 0;
}
這樣的話有個缺點,time(NULL)
是一秒才更新一次的,也就是說我們的隨機資料一秒才換一次,太慢了!
有沒有什麼變的更快的隨機數種子?有!windows自帶了一個隨機數發生器:%random%,它的值就是一個隨機整數,可以在命令列裡呼叫。
那接下來就好辦了,我們把這個數傳給rand.exe用來當隨機數種子就行了。
什麼?你不知道怎麼傳?
呃,你知不知道main
函式裡這兩個引數幹嘛用的:int argc, char *argv[]
?
恐怕好多人還不知道,我這裡解釋下,這兩個就是傳入引數,argc
是引數個數,*argv[]
是引數表,從1開始。
知道了這個就好辦了。
@echo off
:loop
rand.exe %random% > data.in
std.exe < data.in > std.out
my.exe < data.in > my.out
fc my.out std.out
if not errorlevel 1 goto loop
pause
goto loop
我們把%random%
當引數傳給rand.exe就行了。
然後程式裡這麼寫:
#include<bits/stdc++.h>
using namespace std;
#define random(a,b) ((a)+rand()%((b)-(a)+1))
stringstream ss;
int main( int argc, char *argv[] )
{
int seed=time(NULL);
if(argc > 1)//如果有引數
{
ss.clear();
ss<<argv[1];
ss>>seed;//把引數轉換成整數賦值給seed
}
srand(seed);
//以上為隨機數初始化,請勿修改
//random(a,b)生成[a,b]的隨機整數
//以下寫你自己的資料生成程式碼
printf("1\n");
int n=10;
int m=random(1,20);
printf("%d %d\n",n,m);
for(int i=0 ; i<n ; ++i)
{
printf(" %d ",random(0,m));
}
printf("\n");
return 0;
}
我這裡用stringstream
把字串轉換成整數,你們也可以用其他辦法。
這麼一來,資料測試效率就得到了上千倍的提升!為了紀念這麼偉大的改進,我們把程式重新命名為狂拽酷炫吊炸天對拍程式.bat
。
這麼一來對拍程式就徹底完成啦!完結撒花!
你只需把my.cpp和std.cpp放在和對拍程式相同的目錄下
my.cpp裡放你自己的程式碼,編譯成my.exe
std.cpp裡放標程,編譯成std.exe
(推薦用dev-c++,單檔案編譯方便)
然後雙擊執行對拍程式,等待它暫停,然後開啟data.in就能看到對拍出來的資料啦~