SPJ 学习笔记

· · 个人记录

前言

本博客只会讲本蒟蒻目前学习到的地方,所以如果有没讲到的地方还请见谅,等蒟蒻有时间学了再说。

虽然本博客都是一些较为浅显的知识,但是写一道验证较为简单的 SPJ 题还是足够的。

另外,本文的 SPJ 都以 C++ 为例。

Step.1 SPJ 基础

Special Judge(简称:spj,别名:checker)是当一道题有多组解时,用来判断答案合法性的程序。--OI Wiki

也就是说,当某道题没有确定的答案时,可以使用一个程序来判断答案是否符合要求。

Testlib.h 就是一个专门用来写 SPJ 的 C++ 库。

像 Codeforces,因为存在 hack 机制,无论是否题目是否存在多组解,都需要写一个 SPJ,就是一个经常使用 SPJ 的 OJ。

一般的 SPJ 程序如下:

#include"testlib.h"
int main(int argc,char *argv[]) {
  registerTestlibCmd(argc,argv);
  // your code
  if(xxx) quitf(_ok,"xxx");
  else quitf(_wa,"xxx");
}

首先,主函数的写法就和平时不太一样,这里传入的两个参数是为了让 SPJ 能够从命令行输入数据,使得 SPJ 可以从标准输入、标准输出和选手输出中读取数据。

registerTestlibCmd(argc,argv); 语句则是用于初始化 checker,在主函数的最前面必须调用一次,直接复制即可。

然后就是自己的代码,但是读入并不能使用 cin 或者 scanf 等方式输入,而需要 testlib.h 内置的读入方式。

在介绍读入方式前,需要先介绍三个结构体:

所以在写 SPJ 的时候,最好别用这三个名字作为某些变量名或者结构体名。

而读入,则需要调用内置于结构体内的函数实现。

比如,我要从标准输入文件中读入一个整数 n,则可以使用以下代码:

inf.readInt();

以下是一些比较常用的读入函数:

在读入后,按照题意直接进行判断,然后返回评测结果给评测机即可。

同样的,不能直接使用 cout 或者 printf 等方式直接输出,而却需要使用 testlib.h 内置的函数 quitf

使用的格式是 quitf(A,B);

其中 A 部分是评测结果的返回值,一下为比较常用的返回值:

除此之外还有:

可能还有其他的,但是大部分 SPJ 其实都不需要了。

除了这些以外,其他正常的错误,如:RE、TLE、MLE 等,就不需要你自己写了,评测机可以自行判断。

那么,B 部分就是返回的评测信息,书写格式和写 printf 没什么区别。

比如一种例子:"Expected %d, but found %d.",a,b 评测机就会在这个测试点先是对应内容。

一般来说,可以写的较为详细,一方面方便自己调试,一方面可以让选手清晰地知道自己哪里错了。

当然,也可以写的很简略或者干脆不写。

以上就是要写一个 SPJ 的基础部分,掌握了就可以写大部分简单的 SPJ。

一下是一个 SPJ 的例子,要求从输入文件中读入一个整数 n,然后从选手输出文件中读入 n 个整数,要求这 n 个整数是一个排列。

#include"testlib.h"
using namespace std;//可加可不加,看个人习惯
int n,a[1000005],p[1000005];
int main(int argc,char *argv[])
{
    registerTestlibCmd(argc,argv);
    n=inf.readInt()
    for(int i=1;i<=n;++i)
    {
        a[i]=ans.readInt();
        if(a[i]<0||a[i]>n) quitf(_wa,"Required a permutation, but the %dth element is within the range [1,%d].",i,n);
        if(p[a[i]]) quitf(_wa,"Required a permutation, but the %dth element is equal to the %dth element.",p[a[i]],i);
        p[a[i]]=i;
    }
    quitf(_ok,"Yes, it is a permutation.")
}

Step.2 随机数

有些时候,我们可能会使用随机数来满足某些需求,不过 testlib.h 禁用了大部分 C++ 原有的库,也就导致一些原本的随机数生成函数也无法使用,比如 rand(),而内置了一些随机数生成函数。

首先,我需要初始化 Generator,使用以下语句即可:

registerGen(argc,argv,1);

大部分情况直接复制上去即可,最后一个参数是 Generator 的版本号,1 已经是最新版本,不需要改动。

那么初始化后,我们就可以使用 rnd 了。

以下是 rnd 的成员函数:

但是需要特别注意的是,testlib.h 的所有随机数生成器,在传入参数相同情况下,生成的数据都保证了一定相同,甚至环境不同,生成的数据也都完全一样,这是为了防止评测波动和方便调试。

但是有些情况,我们又需要完全随机的数据,但是 testlib.hrand() 都禁用了,这时候我们怎么办呢? (不写 SPJ 了)

值得庆幸的是,testlib.h 还没有禁用某些随机数生成器,比如:mt19937

使用一下语句生成一个随机数,并作为传入参数传给 testlib.h 的随机数生成器即可。

mt19937 rng{random_device{}()};
rnd.setSeed(rng());

这样就可以让 SPJ 生成的随机数每次都不一样,不过,这违反了 testlib.h 的初衷,如果这个随机数影响评测结果还是不建议使用,可以在输出文件或者其他地方放一个随机种子,然后使用 SPJ 读入,作为种子一般情况就可以了。

除了有些时候出题人想整活,比如 1% 的概率会返回一个特殊的评测信息(注意,不是评测结果),才推荐使用这个方法生成随机数。

未完待续