1. 程式人生 > >ACM/OI 對拍程式的寫法

ACM/OI 對拍程式的寫法

搞程式設計競賽的同學很多時候都會因為WA但苦苦找不到錯誤資料而苦惱,雖然肉眼debug的能力也很重要,但有的時候一直手打資料測試兩三天也沒有必要。這裡就介紹一種對拍程式的寫法,是我改進過的,自認為效率應該是比較高了。

如果你懶得學實現細節了,想直接使用,那麼下面的內容可以略過了,到這去下載打包好的,裡面有使用教程:
http://pan.baidu.com/s/1boERyGZ

實現細節

首先對拍程式,顧名思義,一個輸入給兩個程式分別跑一遍,看看對不對的上。

那麼牽扯到三個步驟:

  1. 生成一組輸入資料
  2. 把這組資料分別給兩個程式執行,並生成兩組輸出資料
  3. 比較兩組輸出資料

看到這個步驟很多人應該已經有想法了,沒錯用檔案操作能實現,但太麻煩,因為你得修改你自己的程式碼把輸出重定向到一個檔案,這要是一不小心忘記刪重定向直接交了又得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 looperrorlevel 是上一個命令的返回值,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就能看到對拍出來的資料啦~