UOJ 非传统题配置手册

自从我校使用 UOJ 社区版作为校内 OJ 之后,配置非传统题的旅程就从未停歇(

然而是时候停下了(雾

传统题的配置在 vfk 的文档 里已经很详细了。如果不会配置传统题,建议先去看 vfk 的文档。

之后会把所有样题同步到 百度网盘,密码是 v3fa。如果没有那就是咕了。

提交答案

样题:SZOJ2376 easy

这好像是之前的一道模拟赛题。

UOJ 的内置 judger 有一个 feature (?),就是在比赛的时候,提交答案题会测试所有的测试点并给你看评测结果,但只会告诉你每个测试点你是否拿到了分。如果你拿到了分就会给你显示满分。

于是这题分数赛时最低的我终测之后最高(雾

直接来看 problem.conf:

submit_answer on
n_tests 8
input_pre easy
input_suf in
output_pre easy
output_suf out
output_limit 64
use_builtin_judger on
point_score_1 8
point_score_2 16
point_score_3 16
point_score_4 16
point_score_5 16
point_score_6 11
point_score_7 11
point_score_8 6

写上 submit_answer on 表示这是提答题,其他的都跟传统题一样。好像提交文件配置在配置数据的时候就会改掉反正我们 OJ 是这样的,所以不需要管。

函数式交互

样题:SZOJ2506

这事之前 pb 出的一道交互题,然后丢给我造的数据。

仍然是只看 problem.conf:

n_tests 34
n_ex_tests 3
n_sample_tests 3
input_pre data
input_suf in
output_pre data
output_suf out
time_limit 1
memory_limit 128
output_limit 64
n_subtasks 5
// ...
with_implementer on
token dff2ece271eb429f
use_builtin_judger on
use_builtin_checker wcmp

写上 with_implementer on 表示源代码与 implementer.cpp 一起编译。

token 是一个函数式交互的防 hack 机制,为了区分出输出信息是否是交互器输出的,需要先输出一行字符串表示密码,如果正确说明是交互器的输出。具体实现上 token 由 judger 检查,如果正确就将其直接删除并丢给 checker,所以在评测的详细信息里是看不到的。

I/O 交互

样题:SZOJ 10,11。

SZOJ 10 是字面意义上的 A*B Problem,但多组数据、强制在线、动态构造数据实际上就是现随

use_builtin_judger on
interaction_mode on
n_tests 1
n_ex_tests 0
n_sample_tests 0
input_pre qwq
input_suf in
output_pre qwq
output_suf out
time_limit 1
memory_limit 64
interactor_language C++14
interactor_time_limit 1
interactor_memory_limit 64

写上 interaction_mode on 表示你的程序与 interactor.cpp 从标准输入/输出管道进行通信。然后还要写上 interactor 的一些信息。

传入 interactor 的参数 argv[1] 表示数据包里的输入文件,argv[3] 表示数据包里的输出文件,可以通过 FILE 指针/fstream 读取里面的信息。

interactor 最后要输出 ok 到 stderr,表示交互过程正确。如果啥都没输出或者输出别的东西就会给你判错。所以 interactor 要兼具 checker 的功能。

#include<bits/stdc++.h>
using namespace std;

using UI = unsigned;
using LL = long long;

inline UI randnum()
{
    static UI seed = time(nullptr);
    return seed ^= seed << 5, seed ^= seed >> 7, seed ^= seed << 13;
}

int main()
{
    int N = 50 + randnum() % 51;
    bool accepted = true;
    cout << N << endl;
    for(int i = 1; i <= N; i++) {
        LL a = int(randnum()), b = int(randnum());
        cout << a << ' ' << b << endl;
        LL ans;
        while(true)
        {
            cin >> ans;
            if(ans == a * b)
                break;
        }
    }
    cerr << "ok" << endl;
    return 0;
}

SZOJ 11 是 CF1499G,这题凸显了交互式强制在线相比传统式强制在线的一个功能虽然好像并没啥用,具体见题面。

配置跟上面这题应该没啥大区别,只是放个复杂点的样题更具有参考价值(

Quine

通信题的起手式。

由于内置 judger 不支持将源代码文件作为答案文件,所以你需要自己实现 judger.cpp。还是看 problem.conf:

time_limit 1
memory_limit 256
output_limit 64
use_builtin_judger off

最后一行表示不使用内置 judger。写这一行要求你必须是 OJ 的管理员,如果只有题目管理权限是不行的。

在 judger.cpp 中要调用 uoj_judger.h 的函数作为接口,这里是饭制文档(雾

#include "uoj_judger.h"

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

	report_judge_status_f("Compiling");
	RunCompilerResult c_ret = compile("answer");
	if (!c_ret.succeeded) {
		end_judge_compile_error(c_ret);
	}

	report_judge_status_f("Judging Test");
	TestPointConfig tpc;
	tpc.input_file_name = "/dev/null";
	tpc.output_file_name = work_path + "/output.txt";
	tpc.answer_file_name = work_path + "/answer.code";
	PointInfo po = test_point("answer", 1, tpc);
	add_point_info(po);

	end_judge_ok();
}

是不是看上去超级简单啊(?

然后如果你要自定义 checker,你就可以再写一个 chk.cpp。具体到这题,你可以在 problem.conf 里面写上 use_builtin_checker wcmp 表示全文比较,也可以闲着没事干自己造轮子:

#include "testlib.h"
#include <string>
#include <vector>
#include <sstream>

using namespace std;

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

	std::string strAnswer;

	size_t tot = 0;
	int n = 0;
	while (!ans.eof())
	{
		std::string j = ans.readString();

		if (j == "" && ans.eof())
			break;

		strAnswer = j;
		std::string p = ouf.readString();

		n++;

		if (j != p)
			quitf(_wa, "%d%s lines differ - expected: '%s', found: '%s'", n, englishEnding(n).c_str(), compress(j).c_str(), compress(p).c_str());

		for (int i = 0; i < (int)j.length(); i++)
			if (33 <= j[i] && j[i] <= 126)
				tot++;
	}

	if (tot < 10)
		quitf(_wa, "this code is too short");

	if (n == 1)
		quitf(_ok, "single line: '%s'", compress(strAnswer).c_str());

	quitf(_ok, "%d lines", n);
}

最后,你需要把这两个文件在配置时进行编译,于是要写一个 Makefile 文件:

export INCLUDE_PATH
CXXFLAGS = -I$(INCLUDE_PATH) -O2 -std=c++14

all: chk judger

% : %.cpp
	$(CXX) $(CXXFLAGS) $< -o $@

其中 CXXFLAGS 表示编译命令,all 表示要编译的文件。于是在该目录下调用 make 命令就可以编译这两个文件了。

那使用内置 judger 时不需要做这一步操作是为什么呢?我也不知道,可以去 github 上的 uoj 源码翻翻(雾

最后把这几个文件直接传上去就可以了。

还有一个问题,配置时的编译时限是 15s,所以如果 CTLE 了你要直接联系网站管理员从后台更改编译时限。

单向通信题

样题:SZOJ 7,8。

SZOJ 7 是 UOJ 530 改成了通信题,即给定两人分别一个 (01) 字符串,然后通过单向通信的方式近似求汉明距离。

单向通信的实现方法看上去很暴力,就是先运行一个程序再运行另一个程序,把第一个程序的输出作为第二个程序的输入。

首先你要去 Itst 的博客抄一份 judger。我把里面关于语言设置的东西删掉,全部一刀切只开 C++14删掉问题就解决了问题,然后加了 sample_test。

配置方法在 Itst 的博客也有讲,简单来说就是把 compile_A.cppcompile_B.cppA.hB.hgrader.cpp 都放进 require 文件夹里面,其中 grader 是样例交互库,在自定义测试的时候用到。

#include<bits/stdc++.h>
#include"uoj_judger.h"
using namespace std;

string programme_name_A = "Himeko" , programme_name_B = "Lori";

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 ver = "-std=c++14";
	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,
			"Himeko " + 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,
			"Lori " + 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 sample_test(){
	compile(programme_name_A); compile(programme_name_B);
	int m = conf_int("n_sample_tests");
	bool passed = true;
	for (int i = 1; i <= m; i++) {
		report_judge_status_f("Judging Sample Test #%d", i);
		PointInfo po = Judge_point(-i);
		if(po.scr != 100) passed = false;
		po.num = i; po.scr /= m;
		add_point_info(po);
	} if(passed) tot_score = 100;
	end_judge_ok();
}

void custom_test(){
	report_judge_status_f("Compiling...");
	string langA = "-std=c++14";
	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 if (conf_is("test_sample_only", "on")) sample_test();
        else ordinary_test();
	
	return 0;
}

然后随便写写 problem.conf,注意 problem.conf 就是给 judger 用的,所以只要跟 judger 相对应即可。

n_tests 40
n_ex_tests 0
n_sample_tests 0
input_pre data
input_suf in
output_pre data
output_suf out
time_limit 1
memory_limit 16
output_limit 64
use_builtin_judger off
n_subtasks 1
subtask_end_1 40
subtask_score_1 100
token NTFTXDY_ORZ

然后和刚才一样写 Makefile 然后传上去即可。最后注意要改一改提交文件配置和其他配置:

[
    {
        "name": "Himeko",
        "type": "source code",
        "file_name": "Himeko.code"
    },
    {
        "name": "Lori",
        "type": "source code",
        "file_name": "Lori.code"
    }
]
{
    "view_content_type": "ALL",
    "view_details_type": "ALL",
    "view_all_details_type": "ALL",
    "solution_pdf": false,
    "statement_pdf": false,
    "custom_test_requirement": [
        {
            "name": "Himeko",
            "type": "source code",
            "file_name": "Himeko.code"
        },
        {
            "name": "Lori",
            "type": "source code",
            "file_name": "Lori.code"
        },
        {
            "name": "input",
            "type": "text",
            "file_name": "input.txt"
        }
    ]
}

SZOJ 8 是 JOISC 2021 的 Day3T1,也是一道单向通信题。

双向通信题

SZOJ 9 是 JOISC 2021 的 Day2T3,要求两个程序之间做双向的通信。

实现方法跟 I/O 交互差不多,但因为内置 judger 不支持所以咕了(雾

问 UOJ 开源群的大佬要到了 UOJ454 的数据,这周末回去学习一下(咕咕咕

原文地址:https://www.cnblogs.com/AThousandMoons/p/14698948.html