技巧 - 在OJ上出交互题

有关在OJ上出交互题的一些技巧

本博客所指的OJ仅指SYZOJ,即搭建LOJ使用的在线评测系统。

SYZOJ源码链接(Github仓库): https://github.com/syzoj/syzoj

前言

在校内使用SYZOJ搭建内网OJ已经有些时日了。其实之前就看到有交互题这一说,但一直没有去关注和了解。

今天正颓废时,突然想起了交互题,然后开始便研究。

本以为是testlib,现在才发现SYZOJ有自己特有的交互方式。

正题

SYZOJ帮助中的原话如下:

交互器和选手程序同时运行,交互器的标准输入和标准输出连接了选手程序的标准输出和标准输入 —— 交互通过输入输出进行。
交互器运行时,其目录下会有 input 文件,表示该测试点的输入文件。交互器运行结束后,需要将选手得分写入 score.txt 文件中,并将提供给用户的额外信息输出到标准错误输出(stderr)中。
如果您希望实现 NOI 试题风格的交互(选手通过函数调用与交互器交互),请编写一些头文件作为「附加源文件」并封装标准输入输出的交互。

要学习出交互题,前提是要会出一道传统题。对于出传统题就需要知道的知识就不予讲解。

如何交互

根据帮助,交互通过输入输出进行,那么意为选手程序使用std::cout输出给交互器程序,而交互器程序使用std::cin读取选手的输出,反过来也是如此。

LOJ中交互题系统测试题Guess Number可以自行了解一下。

输入文件

根据帮助,目录下会有 input 文件,表示该测试点的输入文件,那么就需要用文件流操作,我选择使用std::ifstream来读取input文件。交互器从输入文件读取一个整数的实例:

1
2
3
4
5
6
7
#include <fstream>
int n;
int main() {
std::ifstream datin("input");
datin >> n;
return 0;
}

选手得分

将选手得分写入 score.txt 文件中 也是同理,使用std::ofstream打开score.txt文件,写入一个数即可。

可以根据查询次数等信息来确定得分,支持部分正确(0 <= 得分 <= 100)。

额外信息

继续看帮助,将提供给用户的额外信息输出到标准错误输出(stderr)中。额外信息就是测试点的反馈信息,我们常在传统题WA时看到Files user_out and answer differ。给出一个交互器判定选手输出的实例(不完整):

1
2
3
4
5
6
7
#include <iostream>
int n, ans;
int main() {
// main_code
std::cerr << "Wrong answer! The correct answer is " << ans << " ." << std::endl;
return 0;
}

当WA时输出正确的答案以供选手参考,与Special Judge类似。

封装交互

帮助最后一句,是可选的。我们可以封装标准输入输出的交互,我们对于C++而言,可以写一个头文件interactor.h。

我们模拟一个很简单的题来示范interactor.h,选手输出两个数(代表一个坐标)与交互器进行交互,交互器返回一个数表示与终点的曼哈顿距离。

代码如下(只是interactor.h):

1
2
3
4
5
6
7
#include <iostream>
int query(int x, int y){
std::cout << x << " " << y << std::endl << std::flush;
int res;
std::cin >> res;
return res;
}

你可以把它当做附加源文件提供给选手,方便不太懂的选手交互。

数据判断

在交互过程中,交互器需要面对选手提供的各种输出数据。这些数据可能不合法,为了节省代码,介绍一个assert函数,貌似testlib中有类似的函数。

1
2
3
4
5
6
7
8
9
template < typename T >
inline void assert(const T &condition, const std::string &message) {
if(!condition) {
std::cerr << message << std::endl;
score << -1 << std::endl;
exit(0);
}
return;
}

此函数是模板函数,刚入门C++的人可能不太懂。对于这个函数来讲,模板就是为了适用所有类型。condition为一个表达式,message为提供的额外信息。

判断选手输出是否为整数可以用以下代码,如果不是,cin就会出错,然后执行if中的语句(选手的输出等同于交互器的输入)。

1
2
int x, y;
assert(std::cin >> x >> y, "Invalid `x` or `y`!");

交互器判断选手输出的数的范围也是如此:

1
assert(x >= 1 && x <= n && y >= 1 && y <= n, "`x` and `y` must in [1, n]!");

data.yml

写好标程、题面,造好数据之后,对于SYZOJ的交互题必须用data.yml来指明交互器。

交互题与传统题的data.yml差别较小,只是增加交互器名称即可。使用C++11编写名为interactor.cpp的交互器,要在其中增加以下内容:

1
2
3
interactor:
language: cpp11
fileName: interactor.cpp

然后数据文件的ZIP中需要包含interactor.cpp文件。

后续

总体来说比较简单。交互题一般能增大难度、限制选手的算法,也是强制在线的一种实现方法。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×