浅谈oj平台 spj 使用 | 修改问题
LDUOJ 平台开发者spj开发博客
不同的平台的spj使用规则可能不太一样,但是需要改动的地方不是太多
首先:
参数对应
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
第二种:多组输入的特判
示例: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"); }