Bootstrap

浅谈online judge平台 spj [special judge] 使用 | 修改问题

以LDUOJ为例 goto -> github
LDUOJ 平台开发者spj开发博客
不同的平台的spj使用规则可能不太一样,但是需要改动的地方不是太多

首先:

附LDUOJ之父赵京龙学长的特判使用文档地址:https://winterant.github.io/OnlineJudge/web/spj.html

参数对应

args[1] 对应数据的输入
args[2] 对应数据的答案也就是一种允许的情况 或 impossible的情况(下面会讲到)
args[3] 对应用户结果的输出,也就是需要重点关注的地方

返回值

0代表没有问题即 AC
1代表出现问题即 WA
对于有的系统来说
42代表AC,43代表WA,不同的系统可能是不一样的返回值

代码提交

和牛客平台等类似,提交的Java代码主类的类名必须是Main 否则会编译错误
就比如应该是:

public class Main{
	public static void main(){
		/**
		your code
		**/
	}
}

几种spj

第一种:简单的一类特判

比如下面这个比较简单的spj程序:

#include <stdio.h>
#include <math.h>
#include <cstring>
const double eps = 1e-6;
int main(int argc,char *args[])
{
    FILE * f_in=fopen(args[1],"r");
    FILE * f_out=fopen(args[2],"r");
    FILE * f_user=fopen(args[3],"r");
    
    fclose(f_in);
    fclose(f_out);
    fclose(f_user);
    return ret;
}

用文件指针的情况,我们可以直接用fscanf 进行输入输出
格式如下:

fscanf(pnt,"%d",&x);

其中pnt为想要从哪里读取的文件指针。比如要获取用户的输出,就要将pnt替换为f_user
以上方式写出来的spj可能不太严谨,建议换用新的模板,这个模板是存在于文档中:

#include<bits/stdc++.h>
using namespace std;
 
#define AC 0
#define WA 1
 
void jscanf(FILE *&fin, const char *format, ...) {
    va_list args;
    va_start(args, format);   /* 初始化变长参数列表 */
    int ret = vfscanf(fin, format, args);
    va_end(args);         /* 结束使用变长参数列表 */
    if (ret == EOF) {
        printf("When reading data, the program crashes because EOF is encountered in advance.\n");
        exit(WA);
    }
}
 
bool is_whitespace(const char c) {
    return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}
 
//检查用户是否存在多余输出
int read_until_eof(FILE *&fp) {
    char ch = fgetc(fp);
    while (ch != EOF && is_whitespace(ch)) {
        ch = fgetc(fp);
    }
    if (ch != EOF) {
        printf("There is redundant content in user output\n");
        return WA;
    }
    return AC;
}
 
int judge(FILE *&std_in, FILE *&std_out, FILE *&user_out);
 
int main(int argc, char *args[]) {
    if (argc <= 1) {
        printf("Please enter the path of input file, output file, and contestant output file in turn:\n");
        for (int i = 1; i <= 3; i++) {
            args[i] = new char[100];
            scanf("%s", args[i]);
        }
    }
    FILE *in = fopen(args[1], "r");   //测试输入
    FILE *out = fopen(args[2], "r");  //测试输出
    FILE *user = fopen(args[3], "r"); //用户输出
    if (in == NULL)
        printf("No such a file with path %s\n", args[1]);
    if (out == NULL)
        printf("No such a file with path %s\n", args[2]);
    if (user == NULL)
        printf("No such a file with path %s\n", args[3]);
 
    int result = AC;
    if (!in || !out ||!user)
        result = WA;
 
    if (judge(in, out, user) != AC || read_until_eof(user) != AC)
        result = WA;
 
    fclose(in);
    fclose(out);
    fclose(user);
    return result;
}
/*************************** 以上内容请勿修改!!! ***********************/
 
 
/*************************** 请在下面的jduge函数体内编写特判逻辑! ***********************/
int judge(FILE *&std_in, FILE *&std_out, FILE *&user_out) {
    /**
     * std_in: 标准输入文件
     * std_out: 标准输出文件
     * user_out: 用户输出文件
     * 1. 请务必使用jscanf(FILE*, char*, ...)函数读取数据,用法与fscanf()相同
     * 2. 你可以使用标准输出(printf或cout)向参赛选手展示错误原因
     *
     * 以下部分是特判程序,需要出题人根据题意去判断用户的答案是否正确。
     */
    double i, a, b;
    //jscanf(std_in, "%lf", &i); //由于该案例的特判不使用输入数据,故无需从std_in读取数据
    jscanf(std_out, "%lf", &a);//读取标准答案
    jscanf(user_out, "%lf", &b);//读取参赛选手输出的答案
    if (fabs(a - b) > 1e-6) {
        printf("Your result is beyond the scope of the answer! \n");
        printf("The absolute difference between your result and the standard answer is %.9f\n", fabs(a - b));
        return WA;
    }
    printf("Yes\n"); //这句可以不写,仅提示选手通过了一组测试数据
    return AC;
}
第二种:多组输入的特判

示例:UVA10886
在这里插入图片描述
对应的spj程序应该是:

#include <stdio.h>
#include <math.h>
const double eps = 1e-4;
int main(int argc,char *args[])///主函数
{
    FILE * f_in=fopen(args[1],"r");///测试输入
    FILE * f_out=fopen(args[2],"r");///测试输出
    FILE * f_user=fopen(args[3],"r");///用户输出
    int ret=0;///返回值
    int T;
    double a,x;
    char cas[100],num[100];
    fscanf(f_in,"%d",&T);///从输入中读取数据组数T
    while(T--)
    {
        fscanf(f_out,"%s %s %lf",&cas,&num,&a);
        fscanf(f_user,"%s %s %lf",&cas,&num,&x);
        if(fabs(a-x)>eps)
            ret = 1;///Wrong Answer
    }
    fclose(f_in);
    fclose(f_out);
    fclose(f_user);
    return ret;
}
第三种:需要判断特殊情况[impossible]

在这里插入图片描述
则对应的spj就应该为:

#include <stdio.h>
#include <math.h>
#include <cstring>
const double eps = 1e-6;
int main(int argc,char *args[])
{
    FILE * f_in=fopen(args[1],"r");
    FILE * f_out=fopen(args[2],"r");
    FILE * f_user=fopen(args[3],"r");
    int ret = 0;
    double a,x;
    char std[100],usr[100];
    while(fscanf(f_out,"%s",std) == 1 && fscanf(f_user,"%s",usr) == 1){
    	if(strcmp(std,"IMPOSSIBLE") && !strcmp(usr,"IMPOSSIBLE")) 
		{
			ret = 1;
			return ret;
		}
		if(strcmp(usr,"IMPOSSIBLE") && !strcmp(std,"IMPOSSIBLE"))
		{
			ret = 1;
			return ret;
		}
		double sstd = atof(std);
		double uusr = atof(usr);
		if(fabs(sstd - uusr) > eps) {
			ret = 1;
			return ret;
		}
	}
    fclose(f_in);
    fclose(f_out);
    fclose(f_user);
    return ret;
}
第四种:带有[testlib.h]的spj

一般情况下,这种题目的spj都是比较规范的,而且testlib.h是在Github上进行开源的
该头文件的发明者应该是Codeforces的管理员 MikeMirzayanov
只需要将上述中的args[]对应好就没有太大问题
2021-09-16更新
在处理的过程当中,笔者发现大部分的testlib.h类的spj与lduoj是匹配的
所以说在处理的时候如果发现spj编译报错,那么就说明用到的checker.cpp和testlib.h的版本不对应,应该参考附带的testlib.h,然后拼接在一起
在这里插入图片描述
如果发现更改spj之后还不通过std代码,首先可以试试所有的语言的标程,如果还是不通过,可以确定是spj的问题,这里就需要对spj统一换成比较朴实的spj,具体什么格式可以参考文章首部的oj开发者提供的spj使用规范
需要重新实现一下使用到的testlib.h里面的函数方法即可

第五种:GCPC [German Collegiate Programming Contest] 类spj
单个文件的情况

这种情况比较简单
以GCPC2019 Keeping the Dogs Out为例:
在这里插入图片描述
打开可以看到:

#include <fstream>
#include <iostream>
#include <limits>
#include <sstream>
#include <string>
#include <vector>

#include <cassert>

typedef long long ll;

using std::cin;
using std::endl;
using std::ifstream;
using std::ofstream;
using std::string;
using std::vector;

constexpr int CORRECT = 42;///修改
constexpr int INCORRECT = 43;///修改

int main(int argc, char* argv[])
{
    assert(argc == 4);

    ifstream input(argv[1]);
    ifstream answer(argv[2]);
    ofstream debug_output(argv[3] + string("/judgemessage.txt"));///修改

    string s1, s2;
    answer >> s1;
    cin >> s2;

    if (s1 != s2 && (s1 == "impossible" || s2 == "impossible")) {
        debug_output << "Expected: " << s1 << ", got: " << s2 << endl;///被迫对应修改
        return INCORRECT;
    }

    if (s1 != "impossible") {
        ll x, y;
        std::stringstream first_token(s2);
        if (!(first_token >> x) || !(cin >> y)) {
            debug_output << "Too little output" << endl;///被迫对应修改
            return INCORRECT;
        }

        if (!cin || x <= 0 || y <= 0) {
            debug_output << "Presentation Error" << endl;///被迫对应修改
            return INCORRECT;
        }

        if (std::numeric_limits<ll>::max() / x < y) {
            debug_output << "Area too large." << endl;///被迫对应修改
            return INCORRECT;
        }

        ll n;
        input >> n;
        vector<ll> cnt(n + 1);
        for (ll& i: cnt) input >> i;

        ll current_area = 0;
        for (int k = n; k >= 0; --k) {
            ll border_length = 1ll << k;
            current_area += border_length * border_length * cnt[k];
            ll a = (x / border_length) * border_length;
            ll b = (y / border_length) * border_length;
            if (a * b < current_area) {
                debug_output << "Incorrect dimensions, cannot fit all squares of size " << border_length << " and larger." << endl;///被迫对应修改
                return INCORRECT;
            }
        }

        if (current_area != x * y) {
            debug_output << "Area is " << x * y << ", should be " << current_area << "." << endl;///被迫对应修改
            return INCORRECT;
        }
    }

    char c;
    if (cin >> c) {
        debug_output << "Too much output." << endl;///被迫对应修改
        return INCORRECT;
    }

    return CORRECT;
}

改过之后的spj应该是这样子的:
值得一提的是,这里的输入并不是通过文件指针,而是通过流的方式读写文件
而且GCPC比赛平台的argc[3] 应该是将结果反馈给平台的一个参数

#include <fstream>
#include <iostream>
#include <limits>
#include <sstream>
#include <string>
#include <vector>

#include <cassert>

typedef long long ll;

using std::cin;
using std::endl;
using std::ifstream;
using std::ofstream;
using std::string;
using std::vector;

constexpr int CORRECT = 0;
constexpr int INCORRECT = 1;

int main(int argc, char* argv[])
{
//    assert(argc == 4);
    ifstream input(argv[1]);
    ifstream answer(argv[2]);
    ifstream user(argv[3]);
//    ofstream debug_output(argv[3] + string("/judgemessage.txt"));

    string s1, s2;
    answer >> s1;
    user >> s2;

    if (s1 != s2 && (s1 == "impossible" || s2 == "impossible")) {
        return INCORRECT;
    }

    if (s1 != "impossible") {
        ll x, y;
        std::stringstream first_token(s2);
        if (!(first_token >> x) || !(user >> y)) {
            return INCORRECT;
        }

        if (!user || x <= 0 || y <= 0) {
            return INCORRECT;
        }

        if (std::numeric_limits<ll>::max() / x < y) {
            return INCORRECT;
        }

        ll n;
        input >> n;
        vector<ll> cnt(n + 1);
        for (ll& i: cnt) input >> i;

        ll current_area = 0;
        for (int k = n; k >= 0; --k) {
            ll border_length = 1ll << k;
            current_area += border_length * border_length * cnt[k];
            ll a = (x / border_length) * border_length;
            ll b = (y / border_length) * border_length;
            if (a * b < current_area) {
                return INCORRECT;
            }
        }

        if (current_area != x * y) {
            return INCORRECT;
        }
    }

    char c;
    if (user >> c) {
        return INCORRECT;
    }

    return CORRECT;
}
*.h *.cpp的情况

以GCPC 2019 Historical Maths为例:
在这里插入图片描述
我们只需要将两个文件合并在一起就好.h文件放在上面,.cpp文件放在下面
如果遇见了某结构体或者是类里面的某个共有或私有函数没有生命的情况,八成加上using namespace std 可以解决

以这个题为例,合并这些文件之后的spj为:

#include <bits/stdc++.h>
/* Utility functions for writing output validators for the Kattis
 * problem format.
 *
 * The primary functions and variables available are the following.
 * In many cases, the only functions needed are "init_io",
 * "wrong_answer", and "accept".
 *
 * - init_io(argc, argv):
 *        initialization
 *
 * - judge_in, judge_ans, author_out:
 *        std::istream objects for judge input file, judge answer
 *        file, and submission output file.
 *
 * - accept():
 *        exit and give Accepted!
 *
 * - accept_with_score(double score):
 *        exit with Accepted and give a score (for scoring problems)
 *
 * - judge_message(std::string msg, ...):
 *        printf-style function for emitting a judge message (a
 *        message that gets displayed to a privileged user with access
 *        to secret data etc).
 *
 * - wrong_answer(std::string msg, ...):
 *        printf-style function for exitting and giving Wrong Answer,
 *        and emitting a judge message (which would typically explain
 *        the cause of the Wrong Answer)
 *
 * - judge_error(std::string msg, ...):
 *        printf-style function for exitting and giving Judge Error,
 *        and emitting a judge message (which would typically explain
 *        the cause of the Judge Error)
 *
 * - author_message(std::string msg, ...):
 *        printf-style function for emitting an author message (a
 *        message that gets displayed to the author of the
 *        submission).  (Use with caution, and be careful not to let
 *        it leak information!)
 *
 */


#include <sys/stat.h>
#include <cassert>
#include <cstdarg>
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <sstream>

typedef void (*feedback_function)(const std::string &, ...);

const int EXITCODE_AC = 42;///注释 / 修改
const int EXITCODE_WA = 43;///注释 / 修改
const std::string FILENAME_AUTHOR_MESSAGE = "teammessage.txt";
const std::string FILENAME_JUDGE_MESSAGE = "judgemessage.txt";
const std::string FILENAME_JUDGE_ERROR = "judgeerror.txt";
const std::string FILENAME_SCORE = "score.txt";

#define USAGE "%s: judge_in judge_ans feedback_dir < author_out\n"

std::ifstream judge_in, judge_ans;
std::istream author_out(std::cin.rdbuf());///修改

char *feedbackdir = NULL;

void vreport_feedback(const std::string &category,
		const std::string &msg,
		va_list pvar) {
	std::ostringstream fname;
	if (feedbackdir)
		fname << feedbackdir << '/';
	fname << category;
	FILE *f = fopen(fname.str().c_str(), "a");
	assert(f);
	vfprintf(f, msg.c_str(), pvar);
	fclose(f);
}

void report_feedback(const std::string &category, const std::string &msg, ...) {
	va_list pvar;
	va_start(pvar, msg);
	vreport_feedback(category, msg, pvar);
}

void author_message(const std::string &msg, ...) {
	va_list pvar;
	va_start(pvar, msg);
	vreport_feedback(FILENAME_AUTHOR_MESSAGE, msg, pvar);
}

void judge_message(const std::string &msg, ...) {
	va_list pvar;
	va_start(pvar, msg);
	vreport_feedback(FILENAME_JUDGE_MESSAGE, msg, pvar);
}

void wrong_answer(const std::string &msg, ...) {
	va_list pvar;///注释 / 修改
	va_start(pvar, msg);
	vreport_feedback(FILENAME_JUDGE_MESSAGE, msg, pvar);
	exit(EXITCODE_WA);
}

void judge_error(const std::string &msg, ...) {
	va_list pvar;///注释 / 修改
	va_start(pvar, msg);
	vreport_feedback(FILENAME_JUDGE_ERROR, msg, pvar);
	assert(0);
}

void accept() {
	exit(EXITCODE_AC);
}

void accept_with_score(double scorevalue) {
	report_feedback(FILENAME_SCORE, "%.9le", scorevalue);
	exit(EXITCODE_AC);
}


bool is_directory(const char *path) {
	struct stat entry;
	return stat(path, &entry) == 0 && S_ISDIR(entry.st_mode);
}

void init_io(int argc, char **argv) {
	if(argc < 4) {///注释 / 修改
		fprintf(stderr, USAGE, argv[0]);///注释 / 修改
		judge_error("Usage: %s judgein judgeans feedbackdir [opts] < userout", argv[0]);///注释 / 修改
	}///注释 / 修改
///注释 / 修改
	// Set up feedbackdir first, as that allows us to produce feedback///注释 / 修改
	// files for errors in the other parameters.///注释 / 修改
	if (!is_directory(argv[3])) {///注释 / 修改
		judge_error("%s: %s is not a directory\n", argv[0], argv[3]);///注释 / 修改
	}///注释 / 修改
	feedbackdir = argv[3];///注释 / 修改

	judge_in.open(argv[1], std::ios_base::in);
	if (judge_in.fail()) {
		judge_error("%s: failed to open %s\n", argv[0], argv[1]);
	}

	judge_ans.open(argv[2], std::ios_base::in);
	if (judge_ans.fail()) {
		judge_error("%s: failed to open %s\n", argv[0], argv[2]);
	}

	author_out.rdbuf(std::cin.rdbuf());///注释 / 修改
}

using namespace std;
using ll = long long;
using vl = vector<ll>;
#define sz(c) ll((c).size())
#define FOR(i,a,b) for(ll i = (a); i < (b); i++)
#define FORD(i,a,b) for(ll i = ll(b) - 1; i >= (a); i--)

const ll MAX_BASE = (2LL << 60) + 1;

ll check_and_parse_author(const string& to_parse) {
  //first pass; check for invalid character
  if(sz(to_parse) == 0) wrong_answer("Submission provided empty string to parse.\n");
  if(to_parse[0] == '+' && sz(to_parse) == 1) wrong_answer("Answer is not a number.\n");
  if(to_parse[0] != '+' && (to_parse[0] < '0' || '9' < to_parse[0])) wrong_answer("Answer contains invalid character.\n");
  FOR(i,1,sz(to_parse)) if(to_parse[i] < '0' || '9' < to_parse[i]) wrong_answer("Answer contains invalid character.\n");

  //second pass; calculate answer base
  ll base = 0;
  if(to_parse[0] != '+') base = to_parse[0] - '0';
  FOR(i,1,sz(to_parse)) {
    // avoid overflow
    if(MAX_BASE / 10 < base) return MAX_BASE;
    base = base*10 + (to_parse[i] - '0');
  }
  return base;
}
void multiply(vl &a, vl &b, vl &res, ll base) {
  res.assign(sz(res), 0);
  FOR(i,0,sz(a)) {
    FOR(j,0,sz(b)) {
      res[i + j] += a[i] * b[j];
      res[i + j + 1] += res[i + j] / base;
      res[i + j] %= base;
    }
  }
  FOR(i,0,sz(res) - 1) {
    res[i + 1] += res[i] / base;
    res[i] %= base;
  }
}

ll compare(vl &a, vl &b) {
  if(sz(a) < sz(b)) {
    FOR(i,sz(a),sz(b)) if(b[i] != 0) return -1;
  }
  if(sz(a) > sz(b)) {
    FOR(i,sz(b), sz(a)) if(a[i] != 0) return 1;
  }
  FORD(i,0,min(sz(a),sz(b))) {
    if(a[i] - b[i] != 0) return a[i] - b[i];
  }
  return 0;
}

int main(int argc, char **argv) {
  init_io(argc,argv);///注释 / 修改这个函数

  string a_ans, j_ans;
  char foo;
  judge_ans >> j_ans;
  if(!( author_out >> a_ans)) wrong_answer("Less output than expected.\n");
  if(author_out >> foo) wrong_answer("More output than expected.\n");
  transform(a_ans.begin(), a_ans.end(), a_ans.begin(), ::tolower);
  //quick accept
  if(a_ans == j_ans) accept();
  if(a_ans == "impossible") wrong_answer("Submission claims impossible, judge has answer.\n");

  ll base = check_and_parse_author(a_ans);
  if(base < 2) wrong_answer("Invalid base.\n");
  bool differs = false;
  if(j_ans == "impossible") differs = true;

  ll tmp, maxdigit = 1;
  judge_in >> tmp;
  vl a(tmp);
  FORD(i, 0, tmp) {judge_in >> a[i]; maxdigit = max(maxdigit, a[i]);}
  judge_in >> tmp;
  vl b(tmp);
  FORD(i, 0, tmp){ judge_in >> b[i]; maxdigit = max(maxdigit, b[i]);}
  judge_in >> tmp;
  vl prod(tmp);
  FORD(i, 0, tmp){ judge_in >> prod[i]; maxdigit = max(maxdigit, prod[i]);}
  vl res(sz(a) + sz(b) + 1);
  if(base < maxdigit + 1) wrong_answer("Base not greater than all occuring digits.\n");
  multiply(a,b,res,base);
  ll cmp = compare(prod, res);
  if(cmp == 0) {
    if(differs)
      judge_error("Judge answer is 'impossible' but submission gave valid answer.\n");
    accept();
  }
  wrong_answer("Invalid base.\n");
}

由于平台不同的原因,在函数含有某行对文件写操作的代码会出现问题,所以要注释掉,还要将返回的状态码改成0 1,而不是使用42 43
注意有些头文件在Windows平台下并不能使用,会报出编译错误,但是在Linux平台下却是可以的,提交spj之后会编译成功
需要修改的地方已在上面的代码中加入了批注,然后,修改之后应该是:

#include <bits/stdc++.h>
#include <sys/stat.h>
#include <cassert>
#include <cstdarg>
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <sstream>

typedef void (*feedback_function)(const std::string &, ...);

const int EXITCODE_AC = 0;
const int EXITCODE_WA = 1;
const std::string FILENAME_AUTHOR_MESSAGE = "teammessage.txt";
const std::string FILENAME_JUDGE_MESSAGE = "judgemessage.txt";
const std::string FILENAME_JUDGE_ERROR = "judgeerror.txt";
const std::string FILENAME_SCORE = "score.txt";

#define USAGE "%s: judge_in judge_ans feedback_dir < author_out\n"

std::ifstream judge_in, judge_ans;
std::ifstream author_out;

char *feedbackdir = NULL;

void vreport_feedback(const std::string &category,
                      const std::string &msg,
                      va_list pvar) {
	std::ostringstream fname;
	if (feedbackdir)
		fname << feedbackdir << '/';
	fname << category;
	FILE *f = fopen(fname.str().c_str(), "a");
	assert(f);
	vfprintf(f, msg.c_str(), pvar);
	fclose(f);
}

void report_feedback(const std::string &category, const std::string &msg, ...) {
	va_list pvar;
	va_start(pvar, msg);
	vreport_feedback(category, msg, pvar);
}

void author_message(const std::string &msg, ...) {
	va_list pvar;
	va_start(pvar, msg);
	vreport_feedback(FILENAME_AUTHOR_MESSAGE, msg, pvar);
}

void judge_message(const std::string &msg, ...) {
	va_list pvar;
	va_start(pvar, msg);
	vreport_feedback(FILENAME_JUDGE_MESSAGE, msg, pvar);
}

void wrong_answer(const std::string &msg, ...) {
//	va_list pvar;
//	va_start(pvar, msg);
//	vreport_feedback(FILENAME_JUDGE_MESSAGE, msg, pvar);
	exit(EXITCODE_WA);
}

void judge_error(const std::string &msg, ...) {
//	va_list pvar;
//	va_start(pvar, msg);
//	vreport_feedback(FILENAME_JUDGE_ERROR, msg, pvar);
	assert(0);
}

void accept() {
	exit(EXITCODE_AC);
}

void accept_with_score(double scorevalue) {
//	report_feedback(FILENAME_SCORE, "%.9le", scorevalue);
	exit(EXITCODE_AC);
}


bool is_directory(const char *path) {
	struct stat entry;
	return stat(path, &entry) == 0 && S_ISDIR(entry.st_mode);
}

void init_io(int argc, char **argv) {
//	if(argc < 4) {
//		fprintf(stderr, USAGE, argv[0]);
//		judge_error("Usage: %s judgein judgeans feedbackdir [opts] < userout", argv[0]);
//	}
//
//	// Set up feedbackdir first, as that allows us to produce feedback
//	// files for errors in the other parameters.
//	if (!is_directory(argv[3])) {
//		judge_error("%s: %s is not a directory\n", argv[0], argv[3]);
//	}
//	feedbackdir = argv[3];

	judge_in.open(argv[1], std::ios_base::in);
	if (judge_in.fail()) {
		judge_error("%s: failed to open %s\n", argv[0], argv[1]);
	}

	judge_ans.open(argv[2], std::ios_base::in);
	if (judge_ans.fail()) {
		judge_error("%s: failed to open %s\n", argv[0], argv[2]);
	}

	author_out.open(argv[3], std::ios_base::in);
	if (author_out.fail()) {
		judge_error("%s: failed to open %s\n", argv[0], argv[3]);
	}
}

using namespace std;
using ll = long long;
using vl = vector<ll>;
#define sz(c) ll((c).size())
#define FOR(i,a,b) for(ll i = (a); i < (b); i++)
#define FORD(i,a,b) for(ll i = ll(b) - 1; i >= (a); i--)

const ll MAX_BASE = (2LL << 60) + 1;

ll check_and_parse_author(const string& to_parse) {
	//first pass; check for invalid character
	if(sz(to_parse) == 0) wrong_answer("Submission provided empty string to parse.\n");
	if(to_parse[0] == '+' && sz(to_parse) == 1) wrong_answer("Answer is not a number.\n");
	if(to_parse[0] != '+' && (to_parse[0] < '0' || '9' < to_parse[0])) wrong_answer("Answer contains invalid character.\n");
	FOR(i,1,sz(to_parse)) if(to_parse[i] < '0' || '9' < to_parse[i]) wrong_answer("Answer contains invalid character.\n");

	//second pass; calculate answer base
	ll base = 0;
	if(to_parse[0] != '+') base = to_parse[0] - '0';
	FOR(i,1,sz(to_parse)) {
		// avoid overflow
		if(MAX_BASE / 10 < base) return MAX_BASE;
		base = base*10 + (to_parse[i] - '0');
	}
	return base;
}
void multiply(vl &a, vl &b, vl &res, ll base) {
	res.assign(sz(res), 0);
	FOR(i,0,sz(a)) {
		FOR(j,0,sz(b)) {
			res[i + j] += a[i] * b[j];
			res[i + j + 1] += res[i + j] / base;
			res[i + j] %= base;
		}
	}
	FOR(i,0,sz(res) - 1) {
		res[i + 1] += res[i] / base;
		res[i] %= base;
	}
}

ll compare(vl &a, vl &b) {
	if(sz(a) < sz(b)) {
		FOR(i,sz(a),sz(b)) if(b[i] != 0) return -1;
	}
	if(sz(a) > sz(b)) {
		FOR(i,sz(b), sz(a)) if(a[i] != 0) return 1;
	}
	FORD(i,0,min(sz(a),sz(b))) {
		if(a[i] - b[i] != 0) return a[i] - b[i];
	}
	return 0;
}

int main(int argc, char **argv) {
	init_io(argc,argv);

	string a_ans, j_ans;
	char foo;
	judge_ans >> j_ans;
	if(!( author_out >> a_ans)) wrong_answer("Less output than expected.\n");
	if(author_out >> foo) wrong_answer("More output than expected.\n");
	transform(a_ans.begin(), a_ans.end(), a_ans.begin(), ::tolower);
	//quick accept
	if(a_ans == j_ans) accept();
	if(a_ans == "impossible") wrong_answer("Submission claims impossible, judge has answer.\n");

	ll base = check_and_parse_author(a_ans);
	if(base < 2) wrong_answer("Invalid base.\n");
	bool differs = false;
	if(j_ans == "impossible") differs = true;

	ll tmp, maxdigit = 1;
	judge_in >> tmp;
	vl a(tmp);
	FORD(i, 0, tmp) {
		judge_in >> a[i];
		maxdigit = max(maxdigit, a[i]);
	}
	judge_in >> tmp;
	vl b(tmp);
	FORD(i, 0, tmp) {
		judge_in >> b[i];
		maxdigit = max(maxdigit, b[i]);
	}
	judge_in >> tmp;
	vl prod(tmp);
	FORD(i, 0, tmp) {
		judge_in >> prod[i];
		maxdigit = max(maxdigit, prod[i]);
	}
	vl res(sz(a) + sz(b) + 1);
	if(base < maxdigit + 1) wrong_answer("Base not greater than all occuring digits.\n");
	multiply(a,b,res,base);
	ll cmp = compare(prod, res);
	if(cmp == 0) {
		if(differs)
			judge_error("Judge answer is 'impossible' but submission gave valid answer.\n");
		accept();
	}
	wrong_answer("Invalid base.\n");
}
第六种:交互题的spj

本平台暂不支持交互题,所以题库里的交互题目前没有进行处理通过
可以参考洛谷的交互题spj对应写法

第七种[带有testlib.h]的另一种解决方式

…遇见后后续更新
2021-10-21更新
将以下文件入口对应
inf->标准输入 argv[1]
ouf->用户输出 argv[3]
ans->答案结果 argv[2]

void registerTestlibCmd(int argc, char* argv[]) {
	__testlib_ensuresPreconditions();

	testlibMode = _checker;
	__testlib_set_binary(stdin);

	if (argc > 1 && !strcmp("--help", argv[1]))
		__testlib_help();

	// if (argc < 4 || argc > 6)
	// {
	//     quit(_fail, std::string("Program must be run with the following arguments: ") +
	//         std::string("<input-file> <output-file> <answer-file> [<report-file> [<-appes>]]") +
	//         "\nUse \"--help\" to get help information");
	// }

	appesMode = false;

//	if (argc == 3) {///改 
//		resultName = "";
//		appesMode = false;
//	}
//
//	if (argc == 4) {
//		resultName = make_new_file_in_a_dir(argv[3]);
//		appesMode = false;
//	}///改 

	// if (argc == 6)
	// {
	//     if (strcmp("-APPES", argv[5]) && strcmp("-appes", argv[5]))
	//     {
	//         quit(_fail, std::string("Program must be run with the following arguments: ") +
	//                     "<input-file> <output-file> <answer-file> [<report-file> [<-appes>]]");
	//     }
	//     else
	//     {
	//         resultName = argv[4];
	//         appesMode = true;
	//     }
	// }

	inf.init(argv[1], _input);
	ouf.init(argv[3], _output);
	ans.init(argv[2], _answer);/// 改 
}

void registerTestlib(int argc, ...) {
	if (argc  < 3 || argc > 5)
		quit(_fail, std::string("Program must be run with the following arguments: ") +
		     "<input-file> <output-file> <answer-file> [<report-file> [<-appes>]]");

	char** argv = new char*[argc + 1];

	va_list ap;
	va_start(ap, argc);
	argv[0] = NULL;
	for (int i = 0; i < argc; i++) {
		argv[i + 1] = va_arg(ap, char*);
	}
	va_end(ap);

	registerTestlibCmd(argc + 1, argv);
	delete[] argv;
}
第八种 使用validation.h的BAPC2018(较难)

首先站是原始的BAPC后台validation.h文件:

// A header library to safely parse team input.
// It does not support floating points or big integers.

// The easiest way to use this is to symlink it from a validator directory,
// so that it will be picked up when creating a contest zip.

// The default checking behaviour is lenient for both white space and case.
// When validating .in and .ans files, the case_sensitve and space_change_sensitive flags should be
// passed. When validating team output, the flags in problem.yaml should be used.

#include <algorithm>
#include <stdexcept>
#include <fstream>
#include <iostream>
#include <limits>
using namespace std;

const string case_sensitive_flag         = "case_sensitive";
const string space_change_sensitive_flag = "space_change_sensitive";

class Validator {
	const int ret_AC = 42, ret_WA = 43;
	bool case_sensitive;
	bool ws;

  public:
	Validator(int argc, char **argv, istream &in = std::cin) : in(in) {
		for(int i = 0; i < argc; ++i) {
			if(argv[i] == case_sensitive_flag) case_sensitive = true;
			if(argv[i] == space_change_sensitive_flag) ws = true;
		}
		if(ws) in >> noskipws;
	}

	// No copying, no moving.
	Validator(const Validator &) = delete;
	Validator(Validator &&)      = delete;

	// At the end of the scope, check whether the EOF has been reached.
	// If so, return AC. Otherwise, return WA.
	~Validator() {
		eof();
		AC();
	}

	void space() {
		if(ws) {
			char c;
			in >> c;
			if(c != ' ') expected("space", string("\"") + c + "\"");
		}
		// cerr << "read space!\n";
	}

	void newline() {
		if(ws) {
			char c;
			in >> c;
			if(c != '\n') expected("newline", string("\"") + c + "\"");
		}
		// cerr << "read newline!\n";
	}

	// Just read a string.
	string read_string() { return read_string_impl(); }

	// Read a string and make sure it equals `expected`.
	string read_string(string expected) { return read_string_impl(expected); }

	// Read an arbitrary string of a given length.
	string read_string(size_t min, size_t max) {
		string s = read_string();
		if(s.size() < min || s.size() > max)
			expected("String of length between " + to_string(min) + " and " + to_string(max), s);
		return s;
	}

	// Read the string t.
	void test_string(string t) {
		string s = read_string();
		if(case_sensitive) {
			if(s != t) expected(t, s);
		} else {
			if(lowercase(s) != lowercase(t)) expected(t, s);
		}
	}

	// Read a long long.
	long long read_long_long() {
		string s = read_string_impl("", "integer");
		long long v;
		try {
			size_t chars_processed = 0;
			v                      = stoll(s, &chars_processed);
			if(chars_processed != s.size())
				WA("Parsing " + s + " as long long failed! Did not process all characters");
		} catch(const out_of_range &e) {
			WA("Number " + s + " does not fit in a long long!");
		} catch(const invalid_argument &e) { WA("Parsing " + s + " as long long failed!"); }
		return v;
	}

	// Read a long long within a given range.
	long long read_long_long(long long low, long long high) {
		auto v = read_long_long();
		if(low <= v && v <= high) return v;
		expected("integer between " + to_string(low) + " and " + to_string(high), to_string(v));
	}

	int read_int() {
		return read_long_long(std::numeric_limits<int>::min(), std::numeric_limits<int>::max());
	}

	int read_int(int low, int high) {
		int v = read_long_long(std::numeric_limits<int>::min(), std::numeric_limits<int>::max());
		if(low <= v && v <= high) return v;
		expected("integer between " + to_string(low) + " and " + to_string(high), to_string(v));
	}

	// Read a long double.
	long double read_long_double() {
		string s = read_string_impl("", "integer");
		long double v;
		try {
			size_t chars_processed;
			v = stold(s, &chars_processed);
			if(chars_processed != s.size())
				WA("Parsing ", s, " as long double failed! Did not process all characters.");
		} catch(const out_of_range &e) {
			WA("Number " + s + " does not fit in a long double!");
		} catch(const invalid_argument &e) { WA("Parsing " + s + " as long double failed!"); }
		return v;
	}

	// Check the next character.
	bool peek(char c) {
		if(!ws) in >> ::ws;
		return in.peek() == char_traits<char>::to_int_type(c);
	}

	// Return WRONG ANSWER verdict.
	[[noreturn]] void expected(string exp = "", string s = "") {
		if(s.size())
			cout << "Expected " << exp << ", found " << s << endl;
		else if(exp.size())
			cout << exp << endl;
		exit(ret_WA);
	}

	template <typename T>
	[[noreturn]] void WA(T t) {
		cout << t << endl;
		exit(ret_WA);
	}

	template <typename T, typename... Ts>
	[[noreturn]] void WA(T t, Ts... ts) {
		cout << t;
		WA(ts...);
	}

	template <typename... Ts>
	void assert(bool b, Ts... ts) {
		if(!b) WA(ts...);
	}

  private:
	// Read an arbitrary string.
	// expected: if not "", string must equal this.
	// wanted: on failure, print "expected <wanted>, got ..."
	string read_string_impl(string expected_string = "", string wanted = "string") {
		if(ws) {
			char next = in.peek();
			if(isspace(next)) expected(wanted, "whitespace");
		}
		string s;
		if(in >> s) {
			if(!case_sensitive) {
				s               = lowercase(s);
				expected_string = lowercase(expected_string);
			}
			if(!expected_string.empty() && s != expected_string)
				WA("Expected string \"expected\", but found ", s);
			return s;
		}
		expected(wanted, "nothing");
	}

	// Return ACCEPTED verdict.
	[[noreturn]] void AC() { exit(ret_AC); }

	void eof() {
		if(in.eof()) return;
		// Sometimes EOF hasn't been triggered yet.
		if(!ws) in >> ::ws;
		char c = in.get();
		if(c == char_traits<char>::eof()) return;
		expected("EOF", string("\"") + char(c) + "\"");
	}

	// Convert a string to lowercase is matching is not case sensitive.
	string &lowercase(string &s) {
		if(!case_sensitive) return s;
		transform(s.begin(), s.end(), s.begin(), ::tolower);
		return s;
	}

	istream &in;
};

我们尤其需要注意公有成员方法:

Validator(int argc, char **argv, istream &in = std::cin) : in(in) {
		for(int i = 0; i < argc; ++i) {
			if(argv[i] == case_sensitive_flag) case_sensitive = true;
			if(argv[i] == space_change_sensitive_flag) ws = true;
		}
		if(ws) in >> noskipws;
	}

main中的前几行代码:

// Set up the input and answer streams.
	
	std::ifstream in(argv[1]);
	std::ifstream ans(argv[2]);

因为本平台都是使用文件的形式进行判断所提交的代码,这里可以用流的形式进行操作,将42、43改成对应的0、1然后将main中添加如下代码:

// Set up the input and answer streams.
	
	std::ifstream in(argv[1]);
	std::ifstream ans(argv[2]);
	std::ifstream user(argv[3]);
	Validator out(argc, argv, user);
	// 以下代码为输入

然后将公有成员构造方法改成:

Validator(int argc, char **argv, std::ifstream &in) : in(in) {
	for(int i = 0; i < argc; ++i) {
		if(argv[i] == case_sensitive_flag) case_sensitive = true;
		if(argv[i] == space_change_sensitive_flag) ws = true;
	}
	if(ws) in >> noskipws;
}

即可完美解决

;