参考:http://magicoj.com/wiki/spj
简称 SPJ,这是针对用户输出的特判。比如,根据题面求解出来的答案可能存在多个,这样就无法定义一个准确的输出文件来判断用户是否正确,这时就需要 SPJ。或者允许用户的输出在某一精度范围内是正确的。
SPJ 是一个用C、C++写的可执行程序,其返回值决定着判断结果,成功返回(0)表示AC,其他非零值表示WA。
SPJ 的编译参数为:g++ -fno-asm -std=c++11 -O2 ,即已经开启C++11以及O2优化。
请确保 SPJ 程序的正确运行,也未调用与判题无关的系统函数,当 SPJ 在 OJ 中编译出错或运行出错时,OJ 不会给出反馈。
spj 输出到 stderr(标准错误) 的内容将会被记录到用户错误数据点中。
下面给出两种写 SPJ 的方法,采用其中一种即可。
示例一:
#include
#define AC 0
#define WA 1
const double eps = 1e-4;
int main(int argc,char *args[])
{
FILE * f_in = fopen(args[1],”r”);
FILE * f_user = fopen(args[2],”r”);
FILE * f_out = fopen(args[3],”r”);
int ret = AC;
/**************判题逻辑**************/
/**
* 以下判题逻辑代码只是举例:输入有 t 组数据,写 spj 来判断每组数据测试输出与用户输出之差是否在 eps 之内。
*/
int t;
double a, x;
fscanf(f_in, “%d”, &t); //从输入中读取数据组数 t
while (t-–) {
fscanf(f_out, “%lf”, &a); //从读取测试输出
fscanf(f_user, “%lf”, &x); //从读取用户输出
if(fabs(a-x) > eps) {
ret = WA;//Wrong Answer
// 或使用 fprintf(stderr, “结果误差过大\n”); 输出到 标准错误 时,会被错误数据点记录下来
std::cerr << "答案:" << a << " 你的输出:" << x << "。结果误差过大" << std::endl;
break;
}
}
/***********************************/
fclose(f_in);
fclose(f_out);
fclose(f_user);
return ret;
}
示例二:
当前 OJ 采用了跟 Codeforces 一样的 SPJ 标准,即Testlib库。
下载地址: https://github.com/MikeMirzayanov/testlib
当标准输出和选手输出的差小于0.01,那么可以AC,否则WA。
#include "testlib.h"
int main(int argc, char* argv[]) {
registerTestlibCmd(argc, argv);
double pans = ouf.readDouble();
double jans = ans.readDouble();
if (fabs(pans - jans)<0.01)
quitf(_ok, "The answer is correct.");
else
quitf(_wa, "The answer is wrong: expected = %f, found = %f", jans, pans);
}
在程序中,有3个重要的结构体:inf指数据输入文件(本例没有),ouf指选手输出文件,ans指标准答案。
然后,可以从这3表结构体读入数据,不需要用到标准输入输出。如果读到的数据和下面的期望不一致,则spj返回fail结果。
这边继续给出一个多行(不定行数)的spj判断:
#include "testlib.h"
int main(int argc, char* argv[]) {
registerTestlibCmd(argc, argv);
while(!ans.eof()){
double pans = ouf.readDouble();
double jans = ans.readDouble();
ans.readEoln();
if (fabs(pans - jans)>0.01)
quitf(_wa, “The answer is wrong: expected = %f, found = %f”, jans, pans);
}
quitf(_ok, “The answer is correct.”);
return 0;
}
以下读入命令可以使用:
初始化checker,必须在最前面调用一次:void registerTestlibCmd(argc, argv)
读入一个char,指针后移一位:char readChar()
和上面一样,但是只能读到一个字母c:char readChar(char c)
同 readChar(‘ ‘):char readSpace()
读入一个字符串,但是遇到空格、换行、eof为止:string readToken()
读入一个long long/int64:long long readLong()
同上,但是限定范围(包括L,R):long long readLong(long long L, long long R)
读入一个int:int readInt()
同上,但是限定范围(包括L,R):int readInt(int L, int R)
读入一个实数:double readReal()
同上,但是限定范围(包括L,R):double readReal(double L, double R)
读入一个限定范围精度位数的实数:double readStrictReal(double L, double R, int minPrecision, int maxPrecision)
读入string,到换行或者eof为止:string readString(), string readLine()
读入一个换行符: void readEoln()
读入一个eof:void readEof()
输出:
给出 AC:quitf(\_ok, “The answer is correct. answer is %d”, ans);
给出 WA:quitf(\_wa, “The answer is wrong: expected = %f, found = %f”, jans, pans);
测试
使用编译器将该文件编译。在命令行中输入:
./spj in.txt out.txt ans.txt # Linux
spj.exe in.txt out.txt ans.txt # Windows
其中in.txt out.txt ans.txt分别是放在同一目录下的输入文件、选手输出、标准答案。 程序将返回结果。
参考:https://www.cnblogs.com/shenmi20/p/10813542.html
Special Judge
通常的ACM题目包括以下几项内容:题目描述(Description)、输入描述(Input)、输出描述(Output)、样例输入(Sample Input)、样例输出(Sample Out),在后台则包括测试输入(Input Data)和测试输出(Output Data)两项。在评测用户提交的程序正确与否时,系统会将样例输入和测试输入重定向作为程序的标准输入,通过判断程序对应的输出是否与期待的输出完全相同,来判断解答是否正确。
对于同一道题目,用户可能使用各种不同的方法来解答,所以对于某些特殊的题目,其结果可能不唯一,但都符合题目要求。此类题目就需要进行特判(Special Judge)。HUSTOJ便提供了特判功能。
这些题目主要有两种:
1、答案不唯一。
2、控制精度。题目要求输出精度误差在某eps之内。
【使用方法】
第一步,在添加题目时,Special judge勾选Y,以打开系统的特判命令。
第二步,编写spj代码,模板中标注了spj代码区域,自行根据题目要求编写。注意文件名最后以.cc为后缀。
【模板】控制精度为例,对比输出文件和用户结果文件,误差不超过1e-5则Accepted。
#include
#include
#include
#include
#include
#include
using namespace std;
int main(int argc, char* argv[]) {
FILE * f_in=fopen(argv[1],”r”);//测试输入
FILE * f_out=fopen(argv[2],”r”);//测试输出
FILE * f_user=fopen(argv[3],”r”);//用户输出
int ret=0; //AC=0,WA=1
/*****spj代码区域*******/
int T,n;
fscanf(f_in,”%d”,&T);
while(T–)
{
double a,b;
int n;
fscanf(f_in,”%d”,&n);
fscanf(f_out,”%lf”,&a);
fscanf(f_user,”%lf”,&b);
if(fabs(a-b)>1e-5) //WA
{
ret=1;
break;
}
}
/*****spj-end********/
fclose(f_in);
fclose(f_out);
fclose(f_user);
return ret;
}
【模板解释】
HUSTOJ的spj用了命令行参数argc和argv,其中argv[1]指向输入数据(即题目测试输入),argv[2]指向输出数据(题目测试的输出数据,有时不需要),argv[3]指向用户提交的程序所运行输出的文件。分别读取三个文件,做相应的对比。main函数的返回值表示判断结果。 当发现用户的输出文件有误时,return 1; 完全正确时,return 0;
注意读取三个文件时全部用fscanf(C++中fstream库操作也是可以的),读取出来后,只需要自己编写一下如何判断用户的输出是否正确。 可能有些题目很难,不好编写,这时候可以把标程放在spj区域(注意输入都改成fscanf),对比标程运行出来的结果和用户结果的差异是否在题目允许范围之内,来判断用户是否正确。
第三步,将.cc文件上传到服务器对应题目的数据文件夹下(也就是保存测试数据的那个文件夹),可以在上传数据时一同上传
第四步,在服务器上测试数据文件夹下,命令行执行 g++ -o spj spj.cc (spj是编译后的可执行程序,不要改名。spj.cc是你上传的代码文件,名称随意),个别服务器可能还需要权限,再执行一下:chmod +x spj 推荐一个windows下远程服务器命令:Putty,百度搜索即可下载。
第五步,测试。该步骤可省略。在上一步的命令行中,继续执行:./spj data.in data.out user.out ,然后再执行:echo $? 然后窗口会显示1(代表WA)或者0(代表AC),其中data.in 和data.out是测试数据,user.out是用户代码运行的结果。该步操作可模拟进行判断用户输出是否正确。
第六步,提交代码测试题目。建议提交一遍正确代码,再故意改动一下AC代码,提交错误代码,看是否成功WA。