有关对拍的程序

感谢 C++ 思路:
OI 中都有哪些对拍的方法? - ruierqwq 的回答 - 知乎
https://www.zhihu.com/question/433356711/answer/1611183740

以下所讲的对拍都是 Windows 环境。Linux 环境可以稍作修改。


一个很简单的 Windows 下命令行对拍

@echo off
g++ data.cpp -o data.exe -Wall
g++ sol.cpp -o sol.exe -Wall
g++ user.cpp -o user.exe -Wall
:loop
data.exe > data.in
sol.exe < data.in > sol.out
user.exe < data.in > user.out
fc sol.out user.out
if not errorlevel 1 goto loop
fc sol.out user.out > data.log
pause
:end

其中 data.exesol.exeuser.exe 分别为数据生成程序,暴力程序 / 标程,用户程序,均不需要开启文件输入输出。

会一键编译三个程序,然后循环运行三个程序,将答案输出与用户输出比对。若无错就继续循环,否则将日志输出到文本文档,按任意键后结束。

其中存在一些问题就是,很多的比对无错会比较难看。于是考虑写进 C++ 的程序里。

C++ 对拍程序

通过 system () 函数,我们可以在 C++ 程序里调用系统函数。以下程序为 Windows 下的命令,Linux 下请做如下更改:

  • data.exe > data.in./data > data.in
  • sol.exe < data.in > sol.out./sol.exe < data.in > sol.out
  • user.exe < data.in > user.out./user.exe < data.in > user.out
  • fc sol.out user.out > data.logdiff sol.out user.out > data.log
Click to show code
#include <bits/stdc++.h>
using namespace std;

signed main () {
  printf ("Compiling data.cpp ...\n");
  system ("g++ data.cpp -o data.exe -Wall");
  printf ("Compiling sol.cpp ...\n");
  system ("g++ sol.cpp -o sol.exe -Wall");
  printf ("Compiling user.cpp ...\n");
  system ("g++ user.cpp -o user.exe -Wall");
  int st, ed, cst;
  int cnt = 1;
  while (1) {
    system ("data.exe > data.in");
    system ("sol.exe < data.in > sol.out");
    system ("user.exe < data.in > user.out");
    if (system ("fc sol.out user.out > data.log")) {
      printf ("Wrong Answer on test #%d\n", cnt);
      break;
    } else printf("Accepted on test #%d\n", cnt);
    cnt++;
  }
  system ("pause");
  exit (0);
}

接下来,我们可以加入计时系统,统计数据生成暴力、用户程序所需要的时间。
通过使用 clock () 来完成计时。

具体的,

int st, ed, cst;
st = clock (); // 记录开始时间
// 你要运行的东西
ed = clock (); // 记录停止时间
cst = ed - st; // 运行时间 = 停止时间 - 开始时间
printf ("%dms\n", cst); // 输出时间
Click to show code
#include <bits/stdc++.h>
using namespace std;

signed main () {
  printf ("Compiling data.cpp ...\n");
  system ("g++ data.cpp -o data.exe -Wall");
  printf ("Compiling sol.cpp ...\n");
  system ("g++ sol.cpp -o sol.exe -Wall");
  printf ("Compiling user.cpp ...\n");
  system ("g++ user.cpp -o user.exe -Wall");
  int st, ed, cst;
  int cnt = 1;
  while (1) {
    st = clock ();
    system ("data.exe > data.in");
    ed = clock ();
    cst = ed - st;
    printf ("data: %dms\n", cst);
    
    st = clock ();
    system ("sol.exe < data.in > sol.out");
    ed = clock ();
    cst = ed - st;
    printf ("sol: %dms\n", cst);
    
    st = clock ();
    system ("user.exe < data.in > user.out");
    ed = clock ();
    cst = ed - st;
    printf ("user: %dms\n", cst);
    
    if (system ("fc sol.out user.out > data.log")) {
      printf ("Wrong Answer on test #%d\n", cnt);
      break;
    } else printf("Accepted on test #%d\n", cnt);
    cnt++;
  }
  system ("pause");
  exit (0);
}

完整但是不建议考试时使用的程序

加了检查程序文件是否存在。加了一些可选项。可以改数据程序、答案程序和用户程序的名字,切换是否编译,切换是否显示时间。考试的时候并不建议使用,搞这么一大串改来改去的可选项很浪费时间。

Click to show code
#include <bits/stdc++.h>
using namespace std;

/*------------------------------------*/
/*--- Don't touch anything above!! ---*/
/*------------------------------------*/

const char* CONFIG[] = {
"data.cpp", // 你的生成数据的程序
"sol.cpp",  // 答案程序 / 暴力程序
"user.cpp", // 你的程序
"1",        // 是否编译
"1",        // 是否计时
};

/*------------------------------------*/
/*--- Don't touch anything below!! ---*/
/*------------------------------------*/


#define data CONFIG[0]
#define sol  CONFIG[1]
#define user CONFIG[2]
#define comp CONFIG[3] == "1"
#define timi CONFIG[4] == "1"

void checkfileifNexist (const char* str);

signed main () {
  checkfileifNexist (data);
  checkfileifNexist (sol);
  checkfileifNexist (user);
  // ------------------------------------
  char ctmp[127];
  if (comp) {
    printf ("compiling %s...\n", data);
    sprintf (ctmp, "g++ %s -o data.exe -Wall", data);
    const char* compdata = ctmp;
    system (compdata);
    printf ("compiling %s...\n", sol);
    sprintf (ctmp, "g++ %s -o sol.exe -Wall", sol);
    const char* compsol = ctmp;
    system (compsol);
    printf ("compiling %s...\n", user);
    sprintf (ctmp, "g++ %s -o user.exe -Wall", user);
    const char* compuser = ctmp;
    system (compuser);
  }
  int st, ed, cst;
  int cnt = 1;
  while (1) {
    if (timi) st = clock ();
    system ("data.exe > data.in");
    if (timi) ed = clock ();
    if (timi) cst = ed - st;
    if (timi) printf ("data: %dms\n", cst);
    
    if (timi) st = clock ();
    system ("sol.exe < data.in > sol.out");
    if (timi) ed = clock ();
    if (timi) cst = ed - st;
    if (timi) printf ("sol: %dms\n", cst);
    
    if (timi) st = clock ();
    system ("user.exe < data.in > user.out");
    if (timi) ed = clock ();
    if (timi) cst = ed - st;
    if (timi) printf ("user: %dms\n", cst);
    
    if (system ("fc sol.out user.out > data.log")) {
      printf ("Wrong Answer on test #%d\n", cnt);
      break;
    } else printf("Accepted on test #%d\n", cnt);
    cnt++;
  }
  system ("pause");
  exit (0);
}

void checkfileifNexist (const char* str) {
  FILE* fp = fopen (str, "r");
  if (fp == NULL) {
    // not exist, or you don't        \
       have read permission.
    printf ("data maker program \"");
    printf ("%s\" not found.\n", str);
    printf ("not exist, or you don't");
    printf (" have read permission.\n");
    printf ("press any key to leave.\n");
    system ("pause");
    exit (0);
  } else fclose (fp);
}

一些对拍的奇怪使用场合

我们可以用对拍来造数据。两回了两回,我发现以前同学的数据做的有错误,然后我自己的数据生成器生成的数据也不保证合法,我就会采用对拍的方法来查验数据是否合法。

我不是特别擅长这些命令行 QAQ 所以有一些麻烦的地方请谅解

@echo off
echo 编译:data.cpp
g++ data.cpp -o data.exe -Wall
echo 编译:user.cpp
g++ user.cpp -o user.exe -Wall
echo 编译:sol.cpp
g++ sol.cpp -o sol.exe -Wall
echo 全部源代码编译完毕

:loop
data.exe > data.in
user.exe < data.in > user.out
fc user.out blank.txt

if not errorlevel 1 goto loop

sol.exe < data.in > sol.out
fc user.out sol.out

if errorlevel 1 goto loop

echo 哈哈终于有一组合格的数据了
pause
:end

前提条件:你的 user.cpp 必须保证正确,可以是一直和之前对拍时 sol.cpp 输出一致的程序,也可以直接是 sol.cpp 改个文件名写进 user.cpp
和 OI 中对拍的不同之处:user.cppsol.cpp 需要变量的范围不同,保证数据的处理过程中不爆 int 或者不爆 long long 等,高精度可以无视。

最前面是编译三个文件。可以选择删掉一部分或者全部。
然后进行合法性检测:只运行 data.exeuser.exe,将 user.outblank.txt 进行比对,若相同则证明数据不合法,blank.txt 可以是一个空的文本文档,或者是你不想生成但是大量出现的结果;
然后进行运算范围检测:若有运算时数据范围的限制,如「题目保证中间计算过程以及结果均不超过 \(2^{31}−1\)」之类文字,则运行 sol.exe,将 sol.outuser.out 进行比对,若不同就是一个爆了数据范围,另一个没爆(两者变量范围应当不同,见上,使用 #define 解决),可见数据不合法。否则可删除这些条;
通过所有检测之后你的数据就合法了,然后手动复制,拖到一个存放数据的文件夹里,重命名 dataX.indataX.out
没这么用之前从来没想过对拍能这么用

posted @ 2022-10-01 14:07  Reverist  阅读(27)  评论(0编辑  收藏  举报