跳转至

Special Judge

本頁面主要介紹部分評測工具/OJ 的 spj 編寫方法。

簡介

Special Judge(簡稱:spj,別名:checker)是當一道題有多組解時,用來判斷答案合法性的程序。

Warning

spj 還應當判斷文件尾是否有多餘內容,及輸出格式是否正確(如題目要求數字間用一個空格隔開,而選手卻使用了換行)。但是,目前前者只有 Testlib 可以方便地做到這一點,而後者幾乎無人去特意進行這種判斷。

判斷浮點數時應注意 NaN。不合理的判斷方式會導致輸出 NaN 即可 AC 的情況。

在對選手文件進行讀入操作時應該要檢查是否正確讀入了所需的內容,防止造成 spj 的運行錯誤。(部分 OJ 會將 spj 的運行錯誤作為系統錯誤處理)

Note

以下均以 C++ 作為編程語言,以「要求標準答案與選手答案差值小於 1e-3,文件名為 num,單個測試點滿分為 10 分」為例。

Testlib

參見:Testlib/簡介Testlib/Checker

Testlib 是一個 C++ 的庫,用於輔助出題人使用 C++ 編寫算法競賽題。

必須使用 Testlib 作為 spj 的 評測工具/OJ:Codeforces、洛谷、UOJ 等。

可以使用 Testlib 作為 spj 的 評測工具/OJ:LibreOJ (Lyrio)、Lemon、牛客網等。

SYZOJ 2 所需的修改版 Testlib 託管於 pastebin1,但此修改版並未修改交互模式。syzoj/testlib 處託管了一份可以在 SYZOJ 2 上使用交互模式的 Testlib。

Lemon 所需的修改版 Testlib 託管於 GitHub - GitPinkRabbit/Testlib-for-Lemons。注意此版本 Testlib 註冊 checker 時應使用 registerLemonChecker(),而非 registerTestlibCmd()。此版本繼承自 matthew99 的舊版,添加了一些 Testlib 的新功能。

DOMJudge 所需的修改版 Testlib 託管於 cn-xcpc-tools/testlib-for-domjudge。此版本 Testlib 同時可作為 Special Judge 的 checker 和交互題的 interactor。

Arbiter 所需的修改版 Testlib 託管於 testlib-for-arbiter

其他評測工具/OJ 大部分需要按照其 spj 編寫格式修改 Testlib,並將 testlib.h 與 spj 一同上傳;或將 testlib.h 置於 include 目錄。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// clang-format off

#include "testlib.h"
#include <cmath>

int main(int argc, char *argv[]) {
  /*
   * inf:輸入
   * ouf:選手輸出
   * ans:標準輸出
   */
  registerTestlibCmd(argc, argv);

  double pans = ouf.readDouble(), jans = ans.readDouble();

  if (abs(pans - jans) < 1e-3)
    quitf(_ok, "Good job\n");
  else
    quitf(_wa, "Too big or too small, expected %f, found %f\n", jans, pans);
}

Lemon

Note

Lemon 有現成的修改版 Testlib,建議使用 Testlib。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <cmath>
#include <cstdio>

int main(int argc, char* argv[]) {
  /*
   * argv[1]:輸入
   * argv[2]:選手輸出
   * argv[3]:標準輸出
   * argv[4]:單個測試點分值
   * argv[5]:輸出最終得分 (0 ~ argv[4])
   * argv[6]:輸出錯誤報告
   */
  FILE* fin = fopen(argv[1], "r");
  FILE* fout = fopen(argv[2], "r");
  FILE* fstd = fopen(argv[3], "r");
  FILE* fscore = fopen(argv[5], "w");
  FILE* freport = fopen(argv[6], "w");

  double pans, jans;
  fscanf(fout, "%lf", &pans);
  fscanf(fstd, "%lf", &jans);

  if (abs(pans - jans) < 1e-3) {
    fprintf(fscore, "%s", argv[4]);
    fprintf(freport, "Good job\n");
  } else {
    fprintf(fscore, "%d", 0);
    fprintf(freport, "Too big or too small, expected %f, found %f\n", jans,
            pans);
  }
}

Cena

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <cmath>
#include <cstdio>

int main(int argc, char* argv[]) {
  /*
   * FILENAME.in:輸入
   * FILENAME.out:選手輸出
   * argv[1]:單個測試點分值
   * argv[2]:標準輸出
   * score.log:輸出最終得分 (0 ~ argv[1])
   * report.log:輸出錯誤報告
   */
  FILE* fin = fopen("num.in", "r");
  FILE* fout = fopen("num.out", "r");
  FILE* fstd = fopen(argv[2], "r");
  FILE* fscore = fopen("score.log", "w");
  FILE* freport = fopen("report.log", "w");

  double pans, jans;
  fscanf(fout, "%lf", &pans);
  fscanf(fstd, "%lf", &jans);

  if (abs(pans - jans) < 1e-3) {
    fprintf(fscore, "%s", argv[1]);
    fprintf(freport, "Good job\n");
  } else {
    fprintf(fscore, "%d", 0);
    fprintf(freport, "Too big or too small, expected %f, found %f\n", jans,
            pans);
  }
}

CCR

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <cmath>
#include <cstdio>

int main(int argc, char* argv[]) {
  /*
   * stdin:輸入
   * argv[2]:標準輸出
   * argv[3]:選手輸出
   * stdout:L1:輸出最終得分比率 (0 ~ 1)
   * stdout:L2:輸出錯誤報告
   */
  FILE* fout = fopen(argv[3], "r");
  FILE* fstd = fopen(argv[2], "r");

  double pans, jans;
  fscanf(fout, "%lf", &pans);
  fscanf(fstd, "%lf", &jans);

  if (abs(pans - jans) < 1e-3) {
    printf("%d\n", 1);
    printf("Good job\n");
  } else {
    printf("%d\n", 0);
    printf("Too big or too small, expected %f, found %f\n", jans, pans);
  }
}

Arbiter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <cmath>
#include <cstdio>

int main(int argc, char* argv[]) {
  /*
   * argv[1]:輸入
   * argv[2]:選手輸出
   * argv[3]:標準輸出
   * /tmp/_eval.score:L1:輸出錯誤報告
   * /tmp/_eval.score:L2:輸出最終得分
   */
  FILE* fout = fopen(argv[2], "r");
  FILE* fstd = fopen(argv[3], "r");
  FILE* fscore = fopen("/tmp/_eval.score", "w");

  double pans, jans;
  fscanf(fout, "%lf", &pans);
  fscanf(fstd, "%lf", &jans);

  if (abs(pans - jans) < 1e-3) {
    fprintf(fscore, "Good job\n");
    fprintf(fscore, "%d", 10);
  } else {
    fprintf(fscore, "Too big or too small, expected %f, found %f\n", jans,
            pans);
    fprintf(fscore, "%d", 0);
  }
}

HUSTOJ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <cmath>
#include <cstdio>

#define AC 0
#define WA 1

int main(int argc, char* argv[]) {
  /*
   * argv[1]:輸入
   * argv[2]:標準輸出
   * argv[3]:選手輸出
   * exit code:返回判斷結果
   */
  FILE* fin = fopen(argv[1], "r");
  FILE* fout = fopen(argv[3], "r");
  FILE* fstd = fopen(argv[2], "r");

  double pans, jans;
  fscanf(fout, "%lf", &pans);
  fscanf(fstd, "%lf", &jans);

  if (abs(pans - jans) < 1e-3)
    return AC;
  else
    return WA;
}

QDUOJ

相較之下,QDUOJ 略為麻煩。它帶 spj 的題目沒有標準輸出,只能把 std 寫進 spj,待跑出標準輸出後再判斷。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <cmath>
#include <cstdio>

#define AC 0
#define WA 1
#define ERROR -1

double solve(...) {
  // std
}

int main(int argc, char* argv[]) {
  /*
   * argv[1]:輸入
   * argv[2]:選手輸出
   * exit code:返回判斷結果
   */
  FILE* fin = fopen(argv[1], "r");
  FILE* fout = fopen(argv[2], "r");

  double pans, jans;
  fscanf(fout, "%lf", &pans);

  jans = solve(...);
  if (abs(pans - jans) < 1e-3)
    return AC;
  else
    return WA;
}

LibreOJ (SYZOJ 2)

Note

LibreOJ (SYZOJ 2) 有現成的修改版 Testlib,建議使用 Testlib。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <cmath>
#include <cstdio>

int main(int argc, char* argv[]) {
  /*
   * in:輸入
   * user_out:選手輸出
   * answer:標準輸出
   * code:選手代碼
   * stdout:輸出最終得分 (0 ~ 100)
   * stderr:輸出錯誤報告
   */
  FILE* fin = fopen("in", "r");
  FILE* fout = fopen("user_out", "r");
  FILE* fstd = fopen("answer", "r");
  FILE* fcode = fopen("code", "r");

  double pans, jans;
  fscanf(fout, "%lf", &pans);
  fscanf(fstd, "%lf", &jans);

  if (abs(pans - jans) < 1e-3) {
    printf("%d", 100);
    fprintf(stderr, "Good job\n");
  } else {
    printf("%d", 0);
    fprintf(stderr, "Too big or too small, expected %f, found %f\n", jans,
            pans);
  }
}

牛客網

Note

牛客網有現成的修改版 Testlib,建議使用 Testlib。

參見:如何在牛客網出 Special Judge 的編程題

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <cmath>
#include <cstdio>

#define AC 0
#define WA 1

int main(int argc, char* argv[]) {
  /*
   * input:輸入
   * user_output:選手輸出
   * output:標準輸出
   * exit code:返回判斷結果
   */
  FILE* fin = fopen("input", "r");
  FILE* fout = fopen("user_output", "r");
  FILE* fstd = fopen("output", "r");

  double pans, jans;
  fscanf(fout, "%lf", &pans);
  fscanf(fstd, "%lf", &jans);

  if (abs(pans - jans) < 1e-3)
    return AC;
  else
    return WA;
}

DOMJudge

Note

DOMJudge 支持任何語言編寫的 spj,參見:problemarchive.org output validator 格式

DOMJudge 有現成的修改版 Testlib,建議使用 Testlib。

DOMJudge 使用的 Testlib 及導入 Polygon 題目包方式的文檔:https://github.com/cn-xcpc-tools/testlib-for-domjudge

DOMJudge 的 默認比較器 自帶了浮點數帶精度比較,只需要在題目配置的 validator_flags 中添加 float_tolerance 1e-3 即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <cmath>
#include <cstdio>

#define AC 42
#define WA 43
char reportfile[50];

int main(int argc, char* argv[]) {
  /*
   * argv[1]: 輸入
   * argv[2]: 標準輸出
   * argv[3]: 評測信息輸出的文件夾
   * stdin: 選手輸出
   */
  FILE* fin = fopen(argv[1], "r");
  FILE* fstd = fopen(argv[2], "r");
  sprintf(reportfile, "%s/judgemessage.txt", argv[3]);
  FILE* freport = fopen(reportfile, "w");

  double pans, jans;
  scanf("%lf", &pans);
  fscanf(fstd, "%lf", &jans);

  if (abs(pans - jans) < 1e-3) {
    fprintf(freport, "Good job\n");
    return AC;
  } else {
    fprintf(freport, "Too big or too small, expected %f, found %f\n", jans,
            pans);
    return WA;
  }
}

也可以使用 Kattis Problem Tools 提供的頭文件 validate.h 編寫,以實現更加複雜的功能。

參考資料