前言
前幾天搬了個遠古 IOI 的通信丟到聯測去了,vfk 的文檔基本啥都沒說,然后研究了兩天 judger.h 差不多搞清楚怎么在 UOJ 上實現通信評測了。結果數據邊數開小被暴力踩了然后賽時改的數據范圍於是被 down 爆了。
過兩天 gcz 告訴我說有幾個人在 U 裙里問怎么配通信題,我也想順便記錄一下配置過程免得下次配通信的時候又走彎路,當然也有可能沒有下次了,故作此博客。
正文前的提示
在 UOJ,通信題的配置需要自行編寫 judger.cpp
。這意味着你需要擁有超級 root,也就是創建網站時第一個注冊的賬號,僅有這個賬號有不使用內置 judger
的權利。
通信題的正確評測姿勢
今年省選前集訓的時候我和 iotang 已經分別搬了一個通信題了。在 lemon 里 iotang 支持的是一個 grader 同時加多個文件一起編譯的評測方式,也是 JOISC 中的本地評測方式。但這樣的評測方式有一個致命的缺陷是可以在一個文件里實現兩個函數,在另一個文件里啥都不干……如果兩個函數可以實現在同一個文件里的話那么顯然可以把傳入 encode 的信息存全局變量里然后 decode 的時候再拿出來就行了,通信題就沒有意義了。
當時竟然沒有人這么弄,說明選手素質很高
正確的評測方式應該是弄一個 encode_grader
和一個 decode_grader
,先把 encode_grader
和 encode
綁定在一起編譯得到 encode
的輸出,再把這個輸出喂到 decode
和 decode_grader
編譯的文件里得到 decode
的輸出。
當然在 custom test
中用一個 grader
編譯多個文件還是方便一些,可以直接用 judger.h
中的內置評測。
Makefile 的配置
export INCLUDE_PATH
CXXFLAGS = -I$(INCLUDE_PATH) -O2
all: chk judger
% : %.cpp
$(CXX) $(CXXFLAGS) $< -o $@
這個 Makefile 的意義大概是將上傳的文件中必要的 cpp 文件進行編譯。對於通信題配置,有必要修改的只有 CXXFLAGS
和 all
這兩行。
CXXFLAGS
表示的是編譯這些文件的開關,比如要開 C++17 就在這一行的最后面加上 -std=c++17
。
all
表示的是上傳的包中需要編譯的文件的名字,不需要包含 cpp
后綴名。舉例:如果上傳了 chk.cpp,judger.cpp
那么這一行就是 all: chk judger
;如果使用內置 checker
那么這一行就是 all: judger
;如果有校驗器那么這一行就是 all: val judger
你需要注意的是 judger.cpp
總是要編譯的。
judger.h 中的重要接口
如果你只想要一個模板 judger 的話可以跳過這一個部分。如果你想要 DIY 的話可以看一下。
這是本文的一個大頭。judger.h
包含了評測時用到的一些重要的接口。下文會按照它們在 judger.h
中的出現順序將有用的一些函數進行簡單介紹。如果你不知道 judger.h
在哪里,可在 此處 查看。
下面將一些函數的定義刪掉了,把里面的管道交互部分刪掉了(因為我不會弄),然后把一些沒用的都給刪掉了,加了一些函數意義的注釋。然后 RS_XXX
是在 uoj_env.h
里面的定義的一個宏,對應的是評測結果是什么,反正就是 RS_AC,RC_WA,RC_OLE,RC_JGF
之類的。
當然下面的還是很長,因為把 test_point
之類的單點測試參考編寫都保留了。
#include "uoj_env.h"
//這個 .h 包含了所有可能的評測信息如 RS_AC/RS_WA 對應的宏定義。
using namespace std;
/*========================== execute ====================== */
//沒啥用
/*======================== execute End ==================== */
/*========================= file ====================== */
string file_preview(const string &name, const size_t &len = 100);
//讀取 name 文件的前 len 個字符,如果超出則在后面會加上三個省略號。
void file_hide_token(const string &name, const string &token);
//在 name 文件中嘗試讀取 token 並把它刪掉。如果讀取失敗則將結果變為 "Unauthorized output"。
/*======================= file End ==================== */
/*====================== parameter ==================== */
struct RunLimit {//時空限制對應的結構體,名字很容易懂。
int time;
int real_time;
int memory;
int output;
};
const RunLimit RL_DEFAULT = RunLimit(1, 256, 64);
...
//這里有一些默認的限制
struct PointInfo {//測試點結果信息
int num;//測試點編號
int scr;//分數
int ust, usm;//使用的時間和空間
string info, in, out, res;//評測信息/輸入文件名/輸出文件名/評測結果文件名(又好像)
PointInfo(const int &_num, const int &_scr,
const int &_ust, const int &_usm, const string &_info,
const string &_in, const string &_out, const string &_res)
: num(_num), scr(_scr),
ust(_ust), usm(_usm), info(_info),
in(_in), out(_out), res(_res) {
if (info == "default") {
if (scr == 0) {
info = "Wrong Answer";
} else if (scr == 100) {
info = "Accepted";
} else {
info = "Acceptable Answer";
}
}//如果 info 是 "default",則會按照分數給出對應結果
}
};
struct CustomTestInfo {//自定義測試結果信息
int ust, usm;//使用的時間和空間
string info, exp, out;//信息/(可能)輸入文件名/輸出文件名
CustomTestInfo(const int &_ust, const int &_usm, const string &_info,
const string &_exp, const string &_out)
: ust(_ust), usm(_usm), info(_info),
exp(_exp), out(_out) {
}
};
struct RunResult {//評測結果信息
int type;//評測結果種類,這里填 RS_XXX
int ust, usm;
int exit_code;//程序的返回值,如果不為 0 說明 RE 了
static RunResult failed_result(); //構造一個 "Judgement Failed" 的評測狀態信息
static RunResult from_file(const string &file_name); //從 file_name 文件中讀取評測結果,如果讀取失敗返回 "Judgement Failed"
};
struct RunCheckerResult {
//checker 的評測信息
int type;//評測結果種類,這里填 RS_XXX
int ust, usm;
int scr;//分數
string info;//信息
static RunCheckerResult from_file(const string &file_name, const RunResult &rres); //通過 file_name 的 checker 輸出和 rres 中提交程序的評測結果得到合並后的評測結果
static RunCheckerResult failed_result(); //返回一個 "Checker Judgement Failed" 的評測結果
};
struct RunValidatorResult {//校驗器結果信息
int type;
int ust, usm;
bool succeeded;//是否校驗成功
string info;
static RunValidatorResult failed_result(); //返回一個 "Validator Judgment Failed" 的校驗器結果信息
};
struct RunCompilerResult {//評測狀態信息
int type;
int ust, usm;
bool succeeded;
string info;
static RunCompilerResult failed_result(); //返回一個 "Compile Failed"(不同於 CE)的評測信息
};
int problem_id;//字面意思
string main_path;//主目錄
string work_path;//工作目錄(放程序的目錄)
string data_path;//數據存放目錄
string result_path;//存放所有評測結果的目錄,包括編譯、val、chk
int tot_time = 0;//最后顯示的總時間,可以把這里改了變成最大時間之類的
int max_memory = 0;//最大空間
int tot_score = 0;//總分
ostringstream details_out;//這又是啥
//vector<PointInfo> points_info;
map<string, string> config;//存 problem.conf 中所有的 key->val 的映射表
/*==================== parameter End ================== */
/*====================== config set =================== */
void print_config(); //輸出 problem.conf 中的內容到 stderr 里
void load_config(const string &filename); //從 filename 里讀入 problem.conf
string conf_str(const string &key, int num, const string &val);
string conf_str(const string &key, const string &val);
string conf_str(const string &key);
//conf_str(key,(num),(val)):讀取 problem.conf 中 key 對應的結果,在帶有 num 的情況中 key 會轉為 key + "_" + to_string(num) 的形式,如果找不到返回 val,如果沒有 val 返回空串
int conf_int(const string &key, const int &val);
int conf_int(const string &key, int num, const int &val);
int conf_int(const string &key);
//conf_int(key,(num),(val)):和上面一樣,只是會將結果轉成 int 返回,空串返回 0。
/*
key = 提交文件名+"_lang" 可以返回該提交文件的編譯語言
在提交文件配置里面有一個 name 選項,那就是這里的這個提交文件名。
key = checker 可以得到 checker 的文件名
*/
string conf_input_file_name(int num);
string conf_output_file_name(int num);
//conf_(input/output)_file_name (num):通過 problem.conf 得到第 num 個數據的輸入輸出文件名。num<0 時會在開頭加上 ex_ 表示 hack 數據,后面用的是 abs(num) 所以不會出現 ex_test-1 的情況。
RunLimit conf_run_limit(string pre, const int &num, const RunLimit &val);
RunLimit conf_run_limit(const int &num, const RunLimit &val);
//得到運行限制。其中 pre 表示前綴名,比如 checker 的限制就需要加入 "checker" 前綴。無前綴情況就是程序的運行限制。
void conf_add(const string &key, const string &val); //往 conf 里面加東西
bool conf_has(const string &key); //conf 里有沒有這個 key
bool conf_is(const string &key, const string &val); //conf 里 key 對應的取值是不是 val
/*==================== config set End ================= */
/*====================== info print =================== */
template <class T>
inline string vtos(const T &v); //任意類型轉換為 string
inline string htmlspecialchars(const string &s);//輸出 HTML 格式的字符串,如將 < 換為 &:lt
inline string info_str(int id);//將 RS_XXX 格式換為對應信息字符串
inline string info_str(const RunResult &p);//輸出結果的對應信息
void add_point_info(const PointInfo &info, bool update_tot_score = true); //輸出一個測試點的信息,info 表示對應信息,update_tot_score 表示是否將該測試點的分數加入總分里,如 subtask 情況下就不需要加入。
void add_custom_test_info(const CustomTestInfo &info); //輸出自定義測試的結果,info 表示對應信息。
void add_subtask_info(const int &num, const int &scr, const string &info, const vector<PointInfo> &points); //輸出一個 subtask 進去。num 是編號、scr 是分數、info 是整個的評測信息、points 是每個點的評測信息
void end_judge_ok(); //正常結束
void end_judge_judgement_failed(const string &info); //評測掛了導致的結束
void end_judge_compile_error(const RunCompilerResult &res); //CE結束
void report_judge_status(const char *status); //往評測界面輸出信息(如 compiling,judging ... status 就是直接輸出的內容)
bool report_judge_status_f(const char *fmt, ...);//格式化輸出
/*==================== info print End ================= */
/*========================== run ====================== */
struct RunProgramConfig {
};//這大概是一個用於運行提交的代碼程序的信息庫,用處不大
// @deprecated
// will be removed in the future
RunResult vrun_program(
const char *run_program_result_file_name,
const char *input_file_name,
const char *output_file_name,
const char *error_file_name,
const RunLimit &limit,
const vector<string> &rest);
RunResult run_program(const RunProgramConfig &rpc);//直接通過程序信息運行程序
RunResult run_program(
const char *run_program_result_file_name,
const char *input_file_name,
const char *output_file_name,
const char *error_file_name,
const RunLimit &limit, ...);//運行任一程序,程序運行中狀態放在 result_file,ioe 就是stdin/out/err,變長參數是 argv 內容,第一個一定放運行程序的名字。
RunValidatorResult run_validator(
const string &input_file_name,
const RunLimit &limit,
const string &program_name);//跑 validator 檢測 input_file_name 文件,validator 限制為 limit,validator 名字是 program_name。
RunCheckerResult run_checker(
const RunLimit &limit,
const string &program_name,
const string &input_file_name,
const string &output_file_name,
const string &answer_file_name);//跑 checker,文件格式很容易懂。
RunCompilerResult run_compiler(const char *path, ...);
/*
編譯程序。path 是編譯文件的路徑,變長參數是 argv。
舉例:下文中 compile_cpp11 對該函數的調用方式為
run_compiler(path.c_str(),
"/usr/bin/g++-4.8", "-o", name.c_str(), "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", "-std=c++11", NULL);
*/
RunResult run_submission_program(
const string &input_file_name,
const string &output_file_name,
const RunLimit &limit,
const string &name,
RunProgramConfig rpc = RunProgramConfig()); //運行提交的代碼。注意前面的參數會覆蓋 rpc 中的參數。
void prepare_run_standard_program();//把 data 里的 std 拉到 work 里
// @deprecated
// will be removed in the future
RunResult run_standard_program(
const string &input_file_name,
const string &output_file_name,
const RunLimit &limit,
RunProgramConfig rpc = RunProgramConfig()); //跑一遍標算
/*======================== run End ==================== */
/*======================== compile ==================== */
bool is_illegal_keyword(const string &name);//判斷是不是 asm 系列
bool has_illegal_keywords_in_file(const string &name);//檢查文本里有沒有 asm 系列
RunCompilerResult compile_c(const string &name, const string &path = work_path);
...
//懂的都懂
RunCompilerResult compile(const char *name);//就一個 switch
RunCompilerResult compile_c_with_implementer(const string &name, const string &path = work_path);
...
RunCompilerResult compile_with_implementer(const char *name);
//和上面一樣
/*====================== compile End ================== */
/*====================== test ================== */
struct TestPointConfig {//單點測試信息
int submit_answer;//是不是提答 -1->不知道 0->不是 1-> 是
int validate_input_before_test;//測試前是否先跑val
string input_file_name;//輸入文件名
string output_file_name;//輸出文件名
string answer_file_name;//答案文件名
TestPointConfig()
: submit_answer(-1), validate_input_before_test(-1) {
}
void auto_complete(int num);
//如果上面沒有設置是否是提交答案/輸入輸出文件名/是否提前跑一遍val,這里就會自動補全。num 是測試點編號。
};
PointInfo test_point(const string &name, const int &num, TestPointConfig tpc = TestPointConfig()) {
//測試一個測試點。其中 name 是文件名,num 是測試點編號,tpc 是測試點信息
tpc.auto_complete(num);
if (tpc.validate_input_before_test) {
//這里跑了一遍 val
RunValidatorResult val_ret = run_validator(
tpc.input_file_name,
conf_run_limit("validator", 0, RL_VALIDATOR_DEFAULT),
conf_str("validator"));
if (val_ret.type != RS_AC) {
return PointInfo(num, 0, -1, -1,
"Validator " + info_str(val_ret.type),
file_preview(tpc.input_file_name), "",
"");
} else if (!val_ret.succeeded) {
return PointInfo(num, 0, -1, -1,
"Invalid Input",
file_preview(tpc.input_file_name), "",
val_ret.info);
}
}
RunResult pro_ret;
//運行程序
if (!tpc.submit_answer) {
pro_ret = run_submission_program(
tpc.input_file_name.c_str(),
tpc.output_file_name.c_str(),
conf_run_limit(num, RL_DEFAULT),
name);
if (conf_has("token")) {//讀token
file_hide_token(tpc.output_file_name, conf_str("token", ""));
}
if (pro_ret.type != RS_AC) {
return PointInfo(num, 0, -1, -1,
info_str(pro_ret.type),
file_preview(tpc.input_file_name), file_preview(tpc.output_file_name),
"");
}
} else {
pro_ret.type = RS_AC;
pro_ret.ust = -1;
pro_ret.usm = -1;
pro_ret.exit_code = 0;
}
//用 checker 檢查結果
RunCheckerResult chk_ret = run_checker(
conf_run_limit("checker", num, RL_CHECKER_DEFAULT),
conf_str("checker"),
tpc.input_file_name,
tpc.output_file_name,
tpc.answer_file_name);
if (chk_ret.type != RS_AC) {
return PointInfo(num, 0, -1, -1,
"Checker " + info_str(chk_ret.type),
file_preview(tpc.input_file_name), file_preview(tpc.output_file_name),
"");
}
return PointInfo(num, chk_ret.scr, pro_ret.ust, pro_ret.usm,
"default",
file_preview(tpc.input_file_name), file_preview(tpc.output_file_name),
chk_ret.info);
}
PointInfo test_hack_point(const string &name, TestPointConfig tpc) {
//在 Hack 模式下的評測
tpc.submit_answer = false;
tpc.validate_input_before_test = false;
tpc.auto_complete(0);
RunValidatorResult val_ret = run_validator(
tpc.input_file_name,
conf_run_limit("validator", 0, RL_VALIDATOR_DEFAULT),
conf_str("validator"));
if (val_ret.type != RS_AC) {
return PointInfo(0, 0, -1, -1,
"Validator " + info_str(val_ret.type),
file_preview(tpc.input_file_name), "",
"");
} else if (!val_ret.succeeded) {
return PointInfo(0, 0, -1, -1,
"Invalid Input",
file_preview(tpc.input_file_name), "",
val_ret.info);
}
RunLimit default_std_run_limit = conf_run_limit(0, RL_DEFAULT);
prepare_run_standard_program();
RunProgramConfig rpc;
rpc.result_file_name = result_path + "/run_standard_program.txt";
RunResult std_ret = run_submission_program(
tpc.input_file_name,
tpc.answer_file_name,
conf_run_limit("standard", 0, default_std_run_limit),
"std",
rpc);
if (std_ret.type != RS_AC) {
return PointInfo(0, 0, -1, -1,
"Standard Program " + info_str(std_ret.type),
file_preview(tpc.input_file_name), "",
"");
}
if (conf_has("token")) {
file_hide_token(tpc.answer_file_name, conf_str("token", ""));
}
PointInfo po = test_point(name, 0, tpc);
po.scr = po.scr != 100;
return po;
}
CustomTestInfo ordinary_custom_test(const string &name) {
//一般的 custom test 測試
RunLimit lim = conf_run_limit(0, RL_DEFAULT);
lim.time += 2;
string input_file_name = work_path + "/input.txt";
string output_file_name = work_path + "/output.txt";
RunResult pro_ret = run_submission_program(
input_file_name,
output_file_name,
lim,
name);
if (conf_has("token")) {
file_hide_token(output_file_name, conf_str("token", ""));
}
string info;
if (pro_ret.type == RS_AC) {
info = "Success";
} else {
info = info_str(pro_ret.type);
}
string exp;
if (pro_ret.type == RS_TLE) {
exp = "<p>[<strong>time limit:</strong> " + vtos(lim.time) + "s]</p>";
}
return CustomTestInfo(pro_ret.ust, pro_ret.usm,
info, exp, file_preview(output_file_name, 2048));
}
/*====================== test End ================== */
/*======================= conf init =================== */
void main_judger_init(int argc, char **argv);
void judger_init(int argc, char **argv);//初始化。程序開始先運行這一句。
/*===================== conf init End ================= */
judger.cpp(可能是)模板
直接丟我自己配 A+B+C
寫的 judger.cpp
算了,如果有問題可以直接來噴我。
如果你真的很想知道怎么寫,把這個和 judger.h
看完應該是沒有問題的了。
具體使用是 programme_name_A
和 programme_name_B
填入對應的兩個文件名字(第一個是 encode,第二個是 decode),然后在 require
底下丟 (programme_name_A).h
,(programme_name_B).h
,compile_(programme_name_A).cpp
,compile_(programme_name_B).cpp
,grader.cpp
,最后這個 grader
就是一次把兩個文件編譯在一起運行的下發 grader
,然后就能用了。
子任務依賴和拓撲排序是從南外 OJ 白嫖來的(
關於編譯語言,因為初始的 UOJ 只有 C++
和 C++11
兩個選項,所以除了 C++
選項以外其他的 C++
選項一律都是 C++11
。當然你也可以在 compile()
和 custom_test()
里自行修改一下。
然后 token
這個東西 encode
和 decode
里都要輸出。
沒有寫 Hack 的原因是自己試了一下發現沒法重測和加 extests,有沒有老哥教教我 QAQ
里面實現的時候有兩個小的變化是:子任務部分分評測是百分比取 \(\min\),有依賴則與前面的依賴分數取 \(\min\);最終的評測時間是所有測試點時間的 \(\max\)。
#include<bits/stdc++.h>
#include"uoj_judger.h"
using namespace std;
string programme_name_A = "A" , programme_name_B = "B";
vector<vector<int> > get_subtask_dependencies(int n){
vector<vector<int> > dependencies(n + 1, vector<int>());
for (int t = 1; t <= n; t++){
if(conf_str("subtask_dependence", t, "none") == "many"){
string cur = "subtask_dependence_" + vtos(t);
int p = 1;
while(conf_int(cur, p, 0) != 0){
dependencies[t].push_back(conf_int(cur, p, 0));
p++;
}
}else if (conf_int("subtask_dependence", t, 0) != 0)
dependencies[t].push_back(conf_int("subtask_dependence", t, 0));
}
return dependencies;
}
vector<int> subtask_topo_sort(int n, const vector<vector<int> > &dependencies)
{
priority_queue<int> Queue;
vector<int> degree(n + 1, 0), sequence;
for (int t = 1; t <= n; t++) {
for (int x : dependencies[t])
degree[x]++;
}
for (int t = 1; t <= n; t++) {
if (!degree[t])
Queue.push(t);
}
while (!Queue.empty()) {
int u = Queue.top();
Queue.pop();
sequence.push_back(u);
for (int v : dependencies[u]) {
degree[v]--;
if (!degree[v])
Queue.push(v);
}
}
reverse(sequence.begin(), sequence.end());
return sequence;
}
void compile(string name){
report_judge_status_f("compiling %s" , name.c_str());
string lang = conf_str(name + "_language");
if(lang.size() < 3 || string(lang , 0 , 3) != "C++")
end_judge_judgement_failed("Only C++ has been supported. Sorry for the inconvenience");
string ver = lang == "C++" ? "-std=c++98" : "-std=c++11";
RunCompilerResult comp
= run_compiler(work_path.c_str() , "/usr/bin/g++" , "-o" , name.c_str() , "-x" , "c++" , ("compile_" + name + ".cpp").c_str() ,
(name + ".code").c_str() , "-lm" , "-O2" , "-DONLINE_JUDGE" , ver.c_str() , NULL);
if(!comp.succeeded) end_judge_compile_error(comp);
}
vector < vector < int > > subtask_dependencies; vector < int > minscale;
int new_tot_time = 0;
PointInfo Judge_point(int id){
TestPointConfig tpc; tpc.auto_complete(id);
string tempoutput = work_path + "/temp_output.txt";
RunLimit lim = conf_run_limit(id , RL_DEFAULT);
RunResult runA = run_submission_program(
tpc.input_file_name ,
tempoutput ,
lim, programme_name_A);
if(runA.type != RS_AC)
return PointInfo(id, 0, -1, -1,
"Programme A " + info_str(runA.type) ,
file_preview(tpc.input_file_name) ,
file_preview(tempoutput) , "");
if(conf_has("token")) file_hide_token(tempoutput , conf_str("token" , ""));
RunResult runB = run_submission_program(
tempoutput ,
tpc.output_file_name,
lim, programme_name_B);
if(runB.type != RS_AC)
return PointInfo(id, 0, -1, -1,
"Programme B " + info_str(runB.type) ,
file_preview(tempoutput) ,
file_preview(tpc.output_file_name) , "");
if(runA.ust + runB.ust > lim.time * 1000)
return PointInfo(id , 0 , -1 , -1 ,
"Overall Time Limit Exceeded." ,
"" , "" , "");
if(conf_has("token")) file_hide_token(tpc.output_file_name , conf_str("token" , ""));
RunCheckerResult chk_ret = run_checker(
conf_run_limit("checker", id, RL_CHECKER_DEFAULT),
conf_str("checker"),
tpc.input_file_name,
tpc.output_file_name,
tpc.answer_file_name);
if (chk_ret.type != RS_AC) {
return PointInfo(id, 0, -1, -1,
"Checker " + info_str(chk_ret.type),
file_preview(tpc.input_file_name), file_preview(tpc.output_file_name),
"");
}
return PointInfo(id, chk_ret.scr, runA.ust+runB.ust, max(runA.usm,runB.usm),
"default",
file_preview(tpc.input_file_name), file_preview(tpc.output_file_name),
chk_ret.info);
}
void Judge_subtask(int id){
vector<PointInfo> subtask_testinfo;
int mn = 100; for(auto t : subtask_dependencies[id]) mn = min(mn , minscale[t]);
if(!mn){minscale[id] = 0; add_subtask_info(id , 0 , "Skipped" , subtask_testinfo); return;}
int from = conf_int("subtask_end" , id - 1 , 0) , to = conf_int("subtask_end" , id , 0) , total = conf_int("subtask_score" , id , 0);
string statestr = "default";
RunLimit currentlimit = conf_run_limit(id , RL_DEFAULT);
for(int i = from + 1 ; i <= to ; ++i){
report_judge_status_f(("Running on test "+to_string(i)+" on subtask "+to_string(id)).c_str());
PointInfo res = Judge_point(i);
subtask_testinfo.push_back(res); mn = min(mn , res.scr); new_tot_time = max(new_tot_time , res.ust);
if(!mn){statestr = res.info; break;}
}
minscale[id] = mn;
add_subtask_info(id , 1.0 * mn / 100 * total , (statestr == "default" ? (mn == 100 ? "Accepted" : "Acceptable Answer") : statestr) , subtask_testinfo);
}
void ordinary_test(){
compile(programme_name_A); compile(programme_name_B);
int num = conf_int("n_subtasks");
subtask_dependencies = get_subtask_dependencies(num); minscale.resize(num + 1);
vector < int > seq = subtask_topo_sort(num , subtask_dependencies);
for(auto t : seq) Judge_subtask(t);
tot_time = new_tot_time; bool alright = 1; for(int i = 1 ; i <= num ; ++i) alright &= minscale[i] == 100;
if(alright){
int m = conf_int("n_ex_tests");
for (int i = 1; i <= m; i++) {
report_judge_status_f("Judging Extra Test #%d", i);
PointInfo po = Judge_point(-i);
if (po.scr != 100) {
po.num = -1;
po.info = "Extra Test Failed : " + po.info + " on " + vtos(i);
po.scr = -3;
add_point_info(po);
end_judge_ok();
}
}
if (m != 0) {
PointInfo po(-1, 0, -1, -1, "Extra Test Passed", "", "", "");
add_point_info(po);
}
end_judge_ok();
}
end_judge_ok();
}
void custom_test(){
report_judge_status_f("Compiling...");
string langA = conf_str(programme_name_A + "_language") , langB = conf_str(programme_name_B + "_language");
if(langA.size() < 3 || langB.size() < 3 || string(langA , 0 , 3) != "C++" || string(langB , 0 , 3) != "C++")
end_judge_judgement_failed("Only C++ has been supported. Sorry for the inconvenience");
langA = langA == "C++" ? "-std=c++98" : "-std=c++11";
RunCompilerResult comp
= run_compiler(work_path.c_str() , "/usr/bin/g++" , "-o" , "grader" , "-x" , "c++" , "grader.cpp" ,
(programme_name_A + ".code").c_str() , (programme_name_B + ".code").c_str() , "-lm" , "-O2" , "-DONLINE_JUDGE" , langA.c_str() , NULL);
if(!comp.succeeded) end_judge_compile_error(comp);
report_judge_status_f("Judging...");
CustomTestInfo res = ordinary_custom_test("grader");
add_custom_test_info(res);
end_judge_ok();
}
int main(int argc , char** argv){
judger_init(argc , argv);
if (conf_is("custom_test", "on")) custom_test(); else ordinary_test();
return 0;
}
problem.conf
相比一般的 problem.conf
只需要修改 use_builtin_judger
為 off
就行了。
最后一步:提交文件配置
把提交文件配置和其他配置一欄修改成下面兩行
[{"name":"A","type":"source code","file_name":"A.code"},{"name":"B","type":"source code","file_name":"B.code"}]
{"custom_test_requirement":[{"name":"A","type":"source code","file_name":"A.code"},{"name":"B","type":"source code","file_name":"B.code"},{"name":"input","type":"text","file_name":"input.txt"}],"view_content_type":"ALL","view_details_type":"ALL"}
里面的 A
和 B
對應上面的 programme_name_A
和 programme_name_B
。搞完之后大概長這樣
這樣提交的時候就可以提交兩個文件了。類似的也可以進行推廣,比如可以弄一些類似可以提交一個打表的文件然后用程序去讀取它的題目之類的(