Polygon 指北

什么是 Polygon

Polygon 是 EOJ 自带的命题系统。其附带功能包括权限管理、题目规范化、数据修复、数据验证、题目测试等等。

权限控制

Polygon 目前要求用户具有 Red 以上的 rating。也可以受邀由 OJ 管理员(下简称管理员)手动开通。拥有 Polygon 权限的用户称为 Polygon-enabled user,即 Polygon 用户。

注意 Polygon 用户和 管理员权限的区分。Polygon 用户:

  • 具有创建、管理自己的题目,创建、管理自己的比赛,受邀管理他人的题目和比赛的权限。
  • 不具有赋予其他用户 Polygon 权限、管理 OJ 所有题目、管理 OJ 账号、管理判题机、管理博客等功能的权限。

原则上,用户 Polygon 权限开通后,即使 Rating 下跌也不会收回。

题目管理

Problems

Polygon 中的 Problem 与 OJ 上的 Problem 是共享的。也就是说,当在 Polygon 中执行了「创建题目」操作时,OJ 上也会出现相应的 Problem。只不过默认状态下,新创建的 Problem 的 visible 属性是 No 的,而 visible 属性只有管理员能改。

点击右边的蓝色提示板上的 edit 可以快速的编辑题目的来源、难度分级、管理员等信息。Alias 则是一个题目的助记符,帮助你在题目名字没确定的情况下快速地区分题目。

增加的题目管理员需要在具有 Polygon 权限的前提下才能进入比赛的管理界面。但即使不具有 Polygon 权限,也能拥有查看该题代码等权限。

Revisions

Revision 是 Problem 的「正在修改的版本」。顾名思义,一个 Problem 可以对应多个 Revision。

在题目创建后,默认是没有 Revision 的,这时点击 Start New Revision 即可创建 Revision。新创建的 Revision 默认是 Undergoing 状态,表示正在编辑。

修改 Revision 对 OJ 上正在使用的题目是不影响的。类似于个人博客的编辑区,你在编辑区键入任何内容,甚至保存草稿,对外面显示的东西都是不影响的。当对 Revision 进行修改完成后,点右边绿色的按钮 Publish 可以将 Revision 同步到 OJ 上的 Problem 中(在同步的过程中,OJ 上的题目也会完成与判题机端保存的题目的同步)。Publish 后,Revision 的状态会变成 Done。点击 Published 选项卡可以查看当前「用户能看到的版本」。

如果对 Revision 修改并不符合预期,可以放弃当前 Revision,点右边红色的 Discard 按钮即可。Revision 的状态会变为 Discarded。

除了 Undergoing 外,Discarded revision 和 Done revision 都是只读的。意味着该 Revision 已经归档,你不能再执行任何修改。如果确实需要在该 Revision 的基础上进行修改,可以使用 Fork 功能。点击你想要修改的 Revision 旁边的 fork 可以复制出一个新的 Revision。对新的 Revision 的修改不会影响之前只读的 Revision。这样就可以反复修改,直到满意为止。同时也可以追踪历史版本,方便回滚。

还有一种创建 Revision 的方法是 Start new revision from version online,在右侧的小字中出现。这种方法是直接将 OJ 上正在使用的题目强制转换成一个新的 Revision。要认识到这种方法和 fork 之间的区别,首先要注意到 Revision 中带有的信息可能比 OJ 上保存的题目多:比如数据生成器、没有使用的测试数据、测试数据的备注等等,会因为 被认为与判题无关,而不会被上传到 OJ 上。使用这种方法创建的 Revision 将舍去所有非必要的 Revision 细节,只将题目必要的题面、checker、数据等复制过来。(而使用 fork 则会完全克隆,包括所有 revision 中使用的代码、程序、注释、多个 statement 等等。)

Revision 系统实现细节

Polygon 的命题系统完全地贯彻了一个原则,即「修改 = 新建」。改动的时候将对最小的改动部分进行改动,并在数据库中创建新的记录,将相关内容与新的记录关联。这样可以实现完全的历史跟踪(虽然目前跟踪在功能上相对薄弱),并在一定程度上节省存储空间。

Statements

在 Revision 中可以添加多个 Statements。这一点可能与一般的认识相悖:为什么一道题需要多个 Statement?可能需要多个语言?多种格式?但是,事实上,EOJ 目前并不支持一道题多个 Statement。所以就算你在 Revision 中添加了多个 Statement,你也必须指定一个 Active 的(列表第一列的开关打开)。

常见问题:Statement 在编辑的时候 name 和 title 的区别?name 可以理解为 statement 的标识符,你可以填 English、Chinese 之类的东西,如果没什么用就保持默认的 default 就好了。title 则是会显示出来的标题。

Statement 编辑完成后在 Statements 列表中点击 name 一栏的链接进行预览。(如果添加了样例的话,也是可以预览的。样例添加方式见测试数据一节。)

EOJ 建议的排版规范

EOJ 的 statement 使用了一个魔改版的 markdown。该版本最大的特色是 header 会自动降级、换行会转化成 <br>

  • 中英文之间要加空格。
  • 英文和中文标点之间不加空格。
  • 英文标点后要加空格,中文标点后不加空格。
  • 书写变量在变量后马上加数据范围,且不建议使用公式中的括号。例如:$n$ ($1 \le n \le 100$)
  • 英文和英文之间使用半角标点,数据范围的括号使用半角的,其他情况下一律使用全角标点。
  • 公式请使用 TeX,非公式不要使用 TeX。
  • 请注意公式的规范,不要出现类似于 x[i], 1E9 之类的奇怪的东西。
  • 换行请使用两个回车(不建议使用 <br>,看起来会特别拥挤)。

Assets

一个相当常见的需求是在 Revision 中添加图片、压缩包等静态文件。这可以通过上传 Assets 来解决。在 Assets 选项卡直接进行上传即可。上传的 Asset 的文件名前缀会被保存为你输入的 name,中间会插入一段随机字符串防止试题的静态文件链接被「别有心机的 hacker」命中。

注意将 Asset 文件加入到 Statement 中的方法:推荐是点击 Link 一栏中的 Copy 按钮,然后在 Statement 中使用 ![](link) 来加入。这样加入的图片大小也是刚刚好的。如果加入的图片要撑满行宽,可以使用 [](link){:class="fluid"}

Programs

在这里管理当前 Revision 可能用到的可以运行的程序。Polygon 的程序运行在一个特殊的沙盒下。目前仅支持 C++/Java/Python。name 是程序名,tag 是程序的作用。目前支持的 tag 除了特殊的 ignore(无用)之外,有以下类别:

  • checker:输出验证器
  • validator:输入验证器
  • interactor:交互器
  • generator:生成器
  • solution:例程

这套机制来自于 Codeforces 的 testlib,没有接触过的可以自行网搜。EOJ 所有程序编译的时候都可以 #include "testlib.h"testlib.h 提供了一套完备的判题程序工具,并对调用程序的参数进行了规范。本文档中不会提供具体的 checker、validator、interactor 怎么写的说明,这些在 Codeforces 上都有相应的博客和文档介绍;就算没有,在 testlib.h 中也有详细的注释。简单地来说 validator 检验输入的合法性,checker 检验输出的合法性。validator/checker 遵循入紧出松的原则,用通俗的说法说,就是输入要严格合法,输出多出个空格多出个换行也没关系。建议命题的时候用 validator 进行验证,以防止数据不合法等情况发生。

testlib 内置的 checker, validator, interactor 也已经包含在 Polygon 中。使用 Import 功能可以进行导入。内置的每一个程序有什么用,也可以查阅 testlib 的文档。

checker, validator, interactor 这三类程序,每类是可以选一个激活的(将第一列的 Active 勾上)。激活后,程序会被题目所使用。对于 checker 而言,它就会成为程序的输出校验器;对于 interactor 而言,一旦激活,该题目就会变成交互题(当然关闭后,又会重新变成传统题);对于 validator 而言,EOJ 尚不支持 hack 功能。当 hack 功能被支持后,所有提交的测试用例将经由 validator 进行检验,判断合法性。

在默认情况(一个 checker 都没加,或者没激活)下,题目是自带一个 checker 的,在沙箱端称为 defaultspj。该 checker 的功能是:全文比较,忽略行末空格和文末回车。值得注意的是,该程序的运行需要保证数据使用 LF(\n 而非 \r\n)。如果数据使用的是 CRLF,那么会发生未定义行为导致判题异常。

EOJ 支持 checker 返回 _point (exit code: 7)。判题结果会变成 Partial Score。该分数以 100 为默认权重。仅在 OI 赛制 Contest 中有效。(有关默认权重的解释可以查看 Contest 一节的相关内容)

测试数据

添加新的测试数据

测试数据有五种添加方式。推荐四种:

  • 直接手打输入输出
  • 将测试数据命名成类似于 sample01.in, sample01.out, sample02.in, sample02.out 的形式,然后直接(注意不要放在子文件夹下)打包成 .zip 上传,会解包自动匹配。如果是别的常见的格式,比如说 1, 1.a, 2, 2.a 之类的应该也是可以的。匹配规则较为复杂,但应该还是比较智能的。
  • 将所有要使用的输入打成一个 .zip 上传,输出会自动按照空白处理(供稍后生成)。在该模式下,系统会认为压缩包下的所有文件都是输入,有多少个文件就创建多少个 case。
  • 使用生成器生成。

使用生成器生成新的测试数据

生成器目前不支持一次性生成多组数据,如果使用 cyaron 库的,请在本地生成后上传。

能在 OJ 上运行的生成器,需遵循以下规范:

  • 一次运行生成恰好一组测试数据的输入。
  • 在生成器中,所有数据应写到 stdout。(运行时系统会进行重定向)
  • 可以接受命令行参数以控制程序的行为。

命令行参数的使用方法如下:比如说在 Programs 管理中,创建了一个 generator,在 name 字段填了 helloworld,那么在 Generate commands 的输入框中可以填写如下内容:

helloworld 1 2
helloworld 2 3
helloworld 4 10

这样就生成了三组数据,后面的 1 2, 2 3, 4 10 就是传给生成器的输入。

使用生成器生成后,会显示创建了 0 组测试数据。这时候刷新页面,如果一切顺利的话,会发现测试数据增加。(如果失败当然就没有了)可以在 Tasks 选项卡查看生成器运行的详情(时间、占用内存等情况)。

生成输出、运行测试

有了输入之后,比如说要生成输出。这时候可以选中想要生成输出的 case(选中的时候可以全选,可以按住 Shift 选中一系列连续的),然后点击右上角下拉菜单中的 Produce Output。这时,你要确保在 Programs 中有且仅有一个标记为 main correct 的 solution。系统会对每组数据运行 solution,将输入喂入 solution 的标准输入流,并将 solution 输出到标准输出流的内容填进 output。

如果 Programs 里有一个或多个 solution,还可以对这些 solution 进行测试。选中想要测试的 case,然后点击 Run tests,选择想要测试的 solution 即可。

如果要运行 validator,确保 validator 的 active 开关打开,然后运行 Validate Input 即可。

所有测试的结果都会在 Tasks 选项卡中显示,可供查看详情。

注意:

  1. 当前版本的 Polygon 可能存在部分输出错误、不完整的问题,可能需要对于出问题的 case 重新运行。
  2. 进行生成输出操作后,Case 的 ID 会改变,如果之前的网页没刷新继续操作可能会导致错误。
  3. Polygon 内暂不支持对交互题进行测试。

有关 Case 选项的常见问题:

  • 什么是 Output lock?就是说该组数据的输出已经固定了,即使选中它点了 Produce output,输出也不会改变。
  • 什么是 In pretests?仅对部分支持 Pretests 的比赛有用,勾选后,相应的 Case 会进入 Pretest 列表,在支持 Pretest 的比赛中 System tested 未勾选的情况下,只测试 pretests。
  • 什么是 In tests / Activated?顾名思义就是这组数据要不要进行评测。

修改测试数据

要修改测试数据的内容的话,点击 Edit Data,然后可以上传文件,或粘贴内容。(但是还是比较建议把这个测试点直接删了,重新加一个。)

Well form policy

在 Revision 页面上有一个勾,叫 Well form policy。该勾勾上后,上传的数据、生成的数据,会自动将 \r\n 转化成 \n;自动删除 ASCII 小于 32 的字符;自动将连续多个空格变成一个;自动删除行首行末空格;保证文末只有一个回车。但此功能并没有经过时间的检验,建议还是在上传测试数据之前保证合法,以免发生意外。

子任务配置

传统子任务

在传统 OI 比赛中,一道题通常有 10 – 20 个测试点,一个测试点有通过或不通过,通过得 5 – 10 分,不通过得 0 分。要配置这种策略,只要按默认即可。如果测试点分值不一样,则按比例调整分值。比如说一题三个测试点,按 3:4:3,可以调成 6 分,8 分,6 分。样例可以不测试(关闭 In tests),可以调成 0 分。然后在比赛中,一旦将题目总分调成 100,6 分的测试点就自动按照 30 分计算了。

捆绑测试

现代 IOI 比赛支持多次提交,捆绑测试。所谓捆绑测试,就是一个子任务中有多个测试点,要全部通过才能拿到该子任务的分数。此外还可以设置子任务的依赖关系,使得:比如说,只有在通过子任务 1 和 2 的前提下,才会测试子任务 3。

开启子任务测试,首先要在 Revision 标签下勾选 Group Enabled,然后填写需要多少个子任务(多少组),子任务之间的依赖关系,每个子任务占多少分。

  • 子任务依赖关系格式是 <group2>,<group1> 意为:子任务 2 依赖子任务 1。多个依赖关系用 ; 隔开。合法示例:3,2;4,1;5,1;5,2。子任务依赖关系必须形成一个 DAG,否则会无法通过验证。
  • 子任务分值的格式形如 10,20,30,20。依次表示成功通过子任务 1, 2, 3… 的得分。
  • 在 Case 选项卡中设置每个 Case 在哪个子任务中。注意在设置的时候,子任务标号必须为 1 到 n(其中 n 为子任务个数),同时子任务编号小的 Case 必须在编号大的 Case 的前面。例如:Case 1~20 属于第 1 个子任务,Case 21~45 属于第 2 个子任务。

在含有子任务的 Revision 中添加 Sample 和 Pretest 目前属于未定义行为。可能会在未来进行进一步规范化。

代码模板 (Template)

有时候只需要学生编写程序的一部分进行测试。这时候教师需要将程序剩下的部分(例如 main 函数,测试函数)预先写好。可以使用 Template 功能。

使用 Template 需要对每种支持 Template 的语言都编写一个程序。对于不支持的语言,目前的行为是:就当没有 Template。教师需要避免在比赛中添加了不支持 Template 的语言,以免学生提交 CE 造成困惑。

Template 中要求学生替换的部分,用 $$template$$ 指代。学生提交代码时,系统会使用教师提供的模板代码,然后在模板代码中寻找该字符串,并用学生提交的代码去替换,一并提交判题。不过在学生端查看代码时,显示的代码仍然是提交的代码,原则上不会将模板代码展示给学生。

可能存在的很多问题

通过 C/C++ 中强大的 define,学生可以将教师代码中的 main 函数进行重定义,从而自己编写 main 函数。这就绕过了可能存在的某些障碍。当前系统给出的解决方案是:不予解决

通过编译错误信息,学生也可以获取到教师代码中可能想要隐藏的部分,从而获得硬编码的测试数据等。

众所周知 Python 语言的缩进比较特别,这就意味着你不能要求学生写缩进后的一个函数的一部分。

不过为了防止上述情况的发生,还是有几点建议的做法:

  1. 不要将测试数据硬编码在代码中。
  2. 如果要求学生自定义函数的,可以将函数声明写在 main 函数前面,然后将要替换的 $$template$$ 放在 main 函数后面。
  3. 尽量使用类测试。(即不要求写一个函数,而是写一个 Solution 类)

这方面的测试可能像 LeetCode 之类的网站已经做得比较完善。而 EOJ 毕竟是一个传统 OJ,所以这一块还只是处于「勉强能用」的阶段。

管理提交

题目管理员具有该题所有提交的 Rejudge 权限(包括比赛提交)。

表头中的 Rejudge 也是可以点的,会批量重判该题的所有提交(为了保证比赛结果不受影响,在该操作下,比赛的提交默认是跳过的。

比赛管理

所有 Polygon 用户都可以创建比赛。创建出来的比赛可以直接开放,不需要经过管理员审核。比赛可以设置管理员。比赛的管理员与题目的管理员一样,可以管理比赛的题目和提交。如果没有开通 Polygon 权限,那么只可以管理比赛,而不能使用 Polygon 内的功能。

有关比赛时长