☰
NOI Linux 2.0 中 Arbiter 测评系统从零到跑通实战
2026/9/30 10:41:24 网站建设 项目流程

接触过信息学竞赛出题或者校内选拔的朋友,大概都碰到过同一个尴尬:题面写完了,标程也跑通了,可一群人的代码要批量判、要计分、要排名,靠人眼比对输出根本不现实,随便一个疏漏就能把一场比赛的公平性搭进去。这时候 NOI Linux 2.0 里自带的 Arbiter 测评系统就该登场了。它不是一个给选手用的编辑器,而是一套面向出题人和裁判的评测流水线:你把试题数据摆好、把选手源码收进来、点一下评测,它负责编译、逐点运行、比对输出、算分、出排名。这篇文章我打算把它从零到跑通的全过程拆开讲,重点放在那些官方手册一句话带过、但你实操时一定会卡住的地方。不管你只是想给自己的题做本地自测,还是要在校内赛里正式用起来,下面这套流程都能直接照着走。

1. Arbiter 在 NOI 系列赛事里到底管什么

1.1 它解决的是"选手怎么写"和"裁判怎么判"两个完全不同的问题

很多人第一次听到 NOI Linux 2.0,会以为它只是一个"规定好的操作系统",装了它就能比赛。这个理解只对了一半。NOI Linux 2.0 的定位是统一比赛环境:规定用哪个版本的编译器、哪个版本的 Python、哪些编辑器、默认的工作目录长什么样,目的是让所有选手在完全一致的软件栈里写代码,避免"我本地能过、你机器上编译报错"这种扯皮。这一层解决的是"选手怎么写"的问题。

而 Arbiter 测评系统解决的是另一半——"裁判怎么判"。它不关心选手用什么编辑器,只关心三件事:源码能不能编译、跑每个测试点要多久、输出和标准答案是否一致。出题人把这三件事的规则告诉 Arbiter,它就能批量地把几百份代码在一个相对统一的标准下判完,并生成可复查的成绩。这就是为什么正式赛事里选手端和裁判端是两套东西:选手在一台机器上写,裁判在另一套配置了 Arbiter 的环境里判。

理解这个分工很关键,因为后面你会发现,很多"评测结果不对"的问题,既可能出在选手代码上,也可能出在数据摆法或评测配置上,排查时要从这两端分别看。

1.2 Arbiter 和 Lemon、CCR 这类工具的定位差异

市面上的本地评测工具不少,LemonLime、CCR 之类在校内训练里很常见,界面友好、配置简单。为什么正式赛事还要单独用 Arbiter?核心在于可控性和规范性。Arbiter 是跟着 NOI 系列赛事规则走的,评测参数、结果显示、成绩导出都按竞赛流程设计,适合"有申诉、要复核、成绩要留档"的正式场景。而 Lemon 这类工具更适合日常训练,胜在轻量和交互顺手。

下面这张表是我根据实际使用感受整理的差异,帮你判断该用哪个:

维度Arbiter常见轻量本地评测工具
主要场景正式赛事、校内正式选拔个人训练、日常刷题
配置方式逐题设置参数、赛制信息完整通常一键导入、默认参数多
成绩处理支持排名、导出、申诉复核一般只给分数
特殊评测支持接入自定义比较器部分支持,配置相对简化
上手门槛略高,字段多低

如果你只是自己刷题、想快速知道过没过,用轻量工具更省事;但如果你要办一场有几十人参加、结果需要经得起追问的比赛,Arbiter 的规范性就值回学习成本了。我个人的建议是:平时训练用顺手的小工具,一旦要"正式算分",切到 Arbiter。

1.3 官方环境里的 Arbiter 和随手装的测评工具有什么不一样

一个容易被忽略的点:NOI Linux 2.0 里自带的 Arbiter 是和该系统同源发布的,编译环境、运行库、路径习惯都做了配套。这意味着你在系统里直接调用它,不需要额外折腾依赖。而你自己在别的发行版上单独装一个评测工具,很容易遇到库版本对不上、字体缺失、界面乱码之类的琐事。

所以这篇指南的默认前提就是:在 NOI Linux 2.0 这套环境里跑 Arbiter。只要环境对了,后面大半的坑都能绕开。

2. 把 NOI Linux 2.0 跑起来:虚拟机方案里的实际取舍

2.1 镜像获取与虚拟机资源配置怎么给才够用

正式赛场是一台裸机装 NOI Linux 2.0,但日常练习和测试基本都靠虚拟机。镜像从官方渠道获取后,我建议用虚拟机软件挂载安装。资源配置上别抠门:内存给到 4 GB 起步,磁盘 40 GB 左右,CPU 分配 2 核。原因很实际——Arbiter 评测时会同时跑编译和进程,内存太小会出现"评测到一半卡住"或者系统直接卡死的假象,让人误以为是代码的问题,其实是机器扛不住。

另外强烈建议做两个配置。第一,开机前先打一个干净快照,后面无论你把系统折腾成什么样,一键回滚。第二,开启剪贴板共享和拖拽文件功能(装一下对应的增强工具即可),否则你在宿主机和虚拟机之间倒数据会非常痛苦,尤其是反复改测试数据的时候。

有一点要注意:虚拟机磁盘最好用固定大小而不是动态增长,动态磁盘在高负载评测时可能因为扩容出现瞬时卡顿,虽然概率不大,但排查起来很恶心。

2.2 首次进入系统后必须做的几件事

装完系统第一次开机,别急着开 Arbiter,先把基础环境理顺。进入系统后我一般会按这个顺序做:

  1. 打开终端,确认编译器版本。用g++ --version看是不是预期的版本,用python3 --version看 Python 版本。这一步是给后面的评测定基准,因为 Arbiter 的评测参数是按赛事规则配的,你本地版本如果和它预期的不一致,编译行为可能有细微差别。
  2. 设置一个你记得住的登录密码和必要的权限。Arbiter 在某些配置下需要更高权限去写结果目录,提前把权限理清楚,省得评测到一半报"无权限写入"。
  3. 规划工作目录。我习惯在用户主目录下建一个contest文件夹,下面再分source(选手源码)、testdata(测试数据)、result(成绩输出)三个子目录。目录结构提前定好,后面所有路径都从这里出发,能省掉大量找文件的时间。
  4. 装好虚拟机的增强工具,确认文件共享、分辨率、剪贴板都正常。

做完这几步再开测评系统,你会发现后面顺畅很多。我踩过的最蠢的一个坑,就是在没规划目录的情况下东一份西一份地放数据,结果添加试题时路径填错,评测出来一堆 0 分,白折腾一小时。

3. 在 Arbiter 里搭出第一场比赛的骨架

3.1 新建比赛与工作目录的组织方式

打开 Arbiter 后,第一步是新建比赛(New Contest)。它会让你指定一个比赛目录,这个目录就是整场比赛的"根",之后的题、选手、成绩都挂在这个根下面。以常见版本的界面为例(不同版本菜单名称可能略有出入,但逻辑一致),大致是"文件"菜单里找新建比赛,然后选目录、填比赛名称。

这里有个经验:比赛名称用英文或拼音,避免空格和特殊字符。倒不是说中文一定不行,而是涉及路径拼接、导出文件名时,中文和各种转义符号更容易出岔子。我见过有人把比赛名写成带空格的,结果导出的成绩文件路径里多了个空格,后续脚本处理时对不上。

建好比赛后,你的根目录下会生成一套固定的子结构。建议你在建比赛之前,就把之前规划的testdata和source放在这个根目录附近,或者干脆以比赛根目录为中心重新组织一次。核心原则是:让所有相对路径都能从比赛根目录推出来,这样换台机器、换个用户名,只要目录结构一样,整场比赛就能原样复现。

3.2 添加试题时那几栏到底填什么

添加试题是整个流程里信息密度最高的一步,也是新手最容易填错的地方。逐栏拆一下:

  • 题目名称(英文标识):这个标识建议和题面里规定的输入输出文件名保持一致,比如题目叫tree,输入tree.in、输出tree.out。它会影响数据文件的命名习惯。
  • 输入文件名 / 输出文件名:按赛事规则填。有的比赛规定所有题统一用*.in/*.out,有的会规定题目专属名字。务必先确认赛制要求,填错了整题直接全错。
  • 时间限制:单位一般是毫秒。题面里说的"1 秒"就填 1000。这里最容易出的错是把秒当毫秒填,填了个 1,结果所有正常代码全超时。
  • 内存限制:单位 MB。同样按题面填,注意别把 MB 和 KB 搞混。
  • 测试点数据目录:指向你放该题数据的文件夹。目录里数据文件的命名要和系统约定一致,这个第四节细讲。
  • 测试点数量与分值:逐点设置分值,或者统一分值。分值加总一定要和题面总分对上,否则最后出来的总分是错的。

我的做法是:每加完一题,立刻只用一份已知正确的标程去跑一遍,确认能拿满分再继续加下一题。这样如果某题配置有问题,当场就发现了,不用等全部加完再一起炸。这个"加一题验一题"的习惯,是我在连续搞砸两场模拟赛之后逼自己养成的,非常值。

4. 测试数据怎么摆,Arbiter 才不会读错

4.1 输入输出文件名与目录结构的对应关系

数据摆放是"保姆式指南"里最需要讲清楚、但很多教程都含糊带过的一环。Arbiter 读数据时,靠的是你填的输入输出文件名 + 测试点编号这套约定去目录里找文件。常见两种摆放约定:

一种是按题目名加序号,比如题目输入文件名填tree.in,那么数据文件叫tree1.in、tree2.in……对应输出tree1.out、tree2.out……另一种是统一编号,即1.in、1.out、2.in、2.out。

关键点是:目录里有多少组输入输出,就要和你在试题配置里填的测试点数量对齐。少一个,系统判到那一点找不到文件;多一个,可能被判成无效。我曾经遇到有人把标程的输出也放进去了,结果多出几个文件,评测时对照错乱,折腾半天才发现是数据目录被污染了。

所以我的实操建议是:数据目录只放输入输出,不放标程、不放题面、不放草稿。所有额外文件都放到别处去。目录越干净,越不容易出错。摆放完可以用ls数一下文件对数,用wc -l大概看看数据规模,确认没问题再让 Arbiter 去读。

4.2 时间、内存限制与比较方式的选择

填完文件名,接下来是评测参数的"灵魂三件套":时间、内存、比较方式。

时间限制的换算前面说了,秒乘 1000 得毫秒。这里补一个经验值:测试机通常比选手练习机慢,时间限制别卡太死。如果某个测试点标程跑出来是 900 ms,你把限制设成 1000 ms,那在稍慢的评测机上很可能本来正确的代码被判超时。稳妥的做法是留出合理余量,具体留多少按赛制规范来。

内存限制同理,注意标准库和递归深度带来的隐性开销。有些递归很深的题,即使算法本身没问题,也可能因为栈空间不足而崩,这时候要么在评测配置里调大栈空间,要么提醒出题时把递归深度控制在安全范围。

比较方式通常有两档:**逐行比较(忽略行末空格、忽略末尾空行)**是最常用的默认档,适合绝大多数有唯一确定输出的题。但要注意它的边界——它不会帮你判断"格式不标准但内容对"的情况,所以题面里对输出格式的要求必须写清楚。如果题目存在多解,就需要用到特殊评测,下一小节说。

4.3 特殊评测(Special Judge)的接入思路

不是所有题的答案都唯一。像"输出任意一种合法方案"这类题,逐行比较会直接判错,必须上特殊评测(Special Judge)。它的原理不复杂:你写一个比较程序(checker),它同时读入这道题的输入、选手输出、标准答案三份内容,自己判断选手的方案是否合法,然后把结论返回给 Arbiter。

接特殊评测的实操思路是这样:先写并编译好 checker,确认它在命令行下能正确判断几种典型情况(全对、全错、格式诡异但内容对)。然后在试题配置里把比较方式从"逐行比较"改成"使用自定义比较器",并指到你的 checker 上。切记 checker 本身也要测——它写错了比没写更可怕,因为它会安静地把错判成对,或者反过来。我一般会准备三份输出:标程输出、一份人手工构造的合法但不同的输出、一份明显非法的输出,挨个喂给 checker,确认判断都符合预期,再接到比赛里用。

特殊评测的坑还在于接口约定。不同评测系统对 checker 的读入顺序、返回码、输出格式要求不一样,必须严格按你所用版本文档的约定来。这一块没有万能模板,唯一的稳妥办法就是先小范围跑通一个样例题,确认接口对了再批量用。

5. 收代码、点评测、读结果

5.1 选手名单与源码目录的绑定关系

试题配好了,接下来是选手。在 Arbiter 里添加选手,本质上是建立一个"选手标识 → 源码文件"的映射。常见做法是给每个选手一个唯一标识(姓名或考号),然后指定他的源码文件或源码目录。

这里有几条硬性要求,违反了就等着收一堆编译错误:

  • 源码文件名要和题目规定的主文件名一致。如果题面要求提交tree.cpp,选手交了个tree (1).cpp或者我的代码.cpp,系统找不到入口就会判编译失败。
  • 一个选手一道题对应一份源码,别把多份代码堆在一个目录里让系统猜。
  • 目录命名尽量和选手标识对齐,方便你之后核对哪份是谁的。

我的习惯是:收完代码后先自己浏览一遍文件列表,把明显不符合命名规范的挑出来,和来源核对清楚,再导入。批量导入时如果格式不齐,最省事的办法其实是统一重命名一次,而不是指望系统容错。

5.2 评测状态码都代表什么

点击评测后,界面会给每个测试点一个状态。读懂这些状态是排查问题的基础:

状态含义常见诱因
Accepted通过输出与标准一致
Wrong Answer答案错误逻辑错、格式错、比较方式不符
Time Limit Exceeded超时算法复杂度高、死循环、时间限制过紧
Memory Limit Exceeded超内存数组过大、递归过深、内存限制过紧
Runtime Error运行时错误越界、除零、栈溢出
Compile Error编译失败语法错、文件名不对、用了不支持的语法
Presentation Error格式错误多余空格、末尾换行不匹配

有一点要留意:编译错误往往是"整题 0 分",而其他错误是按测试点扣分。所以看到某位选手全 0,先看是不是编译挂了,而不是急着怀疑算法。我遇到过好几次"这题明明有水法能过几个点"的情况,最后发现是文件名写错导致整题编译失败,一个点都没跑到。

5.3 成绩导出与二次核查

评测跑完,Arbiter 通常能给出总分、排名,并支持导出成绩表。正式场景里,导出之后一定要做抽样复核:随机挑几个选手、几个测试点,手动把他的输出和标准答案比一下,确认系统的判定和你的判断一致。

我特别建议对"边界分数"的选手多看一眼——比如差几分就能拿名次的那几位。这不是不信任系统,而是评测参数、时间余量这些设置一旦有偏差,受影响最大的就是接近阈值的人。复核这一步花的时间,远比事后处理申诉要少。

导出格式上,如果后续还要用表格工具处理,注意导出的编码,避免中文姓名出现乱码。这一点在下一节会具体讲。

6. 真实踩坑记录:编译、权限、路径、编码

6.1 编译报错但本地能过的几种情况

这是最让人抓狂的一类问题:选手说他本地能编译,放到评测机上就报错。排查下来,原因集中在几个地方。

第一是编译标准不一致。选手在本地用了较新标准的语法,而评测机按赛事规则用了较旧的标准,某些写法就不被接受。解决办法是让选手在训练阶段就按官方环境的标准来写,别用"我本地能过"当挡箭牌。

第二是使用了非标准扩展或平台相关的库。评测环境是统一的,依赖系统特性的代码很容易挂。

第三是文件名和入口函数的问题。比如源码文件名不符合规定,或者没定义规定的入口,系统根本编译不到那份代码。

遇到这类情况,我的处理顺序是:先看编译报错的完整信息(别只看最后一行),定位到具体是哪一行、哪个符号,再判断是语法问题、标准问题还是环境问题。把完整的编译日志保存下来,既是排查依据,也是给选手的解释材料。

6.2 权限、中文路径与编码的连环坑

Arbiter 在写结果、读数据时要访问文件系统。如果比赛目录的权限设置不当,可能出现"评测能跑但结果存不下来",或者"结果存下来了但导出时读不到"的情况。建议在开评前,把比赛根目录及其子目录的读写权限理清楚,用普通用户能完整走完一遍"新建比赛→评测→导出"流程作为验收标准。

中文路径是个老问题。强烈建议所有和评测相关的路径都用英文:比赛目录、试题数据目录、选手源码目录、导出文件名,一律英文。中文路径不一定立刻报错,但它会在某个你没注意到的环节突然出问题,比如导出乱码、脚本匹配失败。不值得赌。

编码方面,测试数据文件和选手输出最好统一用无 BOM 的 UTF-8 或纯 ASCII。如果数据文件带了 BOM,某些读取方式会把它当成内容的一部分,导致第一个字符比对失败,表现为"明明看着一样却判错"。这种坑不亲眼见过真的很难想到。

6.3 评测结果离谱时的排查链条

当评测结果和你预期差得很远,别一上来就改代码,按这条链条走一遍:

  1. 先单点复现。挑一个判错的测试点,手动把那份输入喂给选手代码,把他的输出和标准答案并排看。八成的问题在这一步就现形了。
  2. 确认比较方式。有没有可能答案对了但格式被判错?换一种比较方式再试一次。
  3. 确认数据无误。标准答案会不会本身就是错的?用标程重新生成一遍,和现有答案比对。
  4. 确认参数。时间、内存限制是不是设得太极端?
  5. 确认路径与命名。系统读到的到底是不是你以为的那个文件?

这条链条的价值在于从最简单、最可能的原因查起。我见过太多人一开始就往"系统有 bug"的方向想,绕了一大圈,最后发现是数据文件命名差了一个序号。按链条走,能让你在几分钟内定位大多数问题。

7. 把它当自测工具用的一些进阶玩法

7.1 用自己当选手做本地对拍式自测

Arbiter 不只是给裁判用的。出题人自己也可以把自己当选手:写一份标程、准备一组测试数据,通过同一套流程跑一遍,验证题面、数据、限制是否自洽。这比单纯用脚本对拍更接近真实评测,因为它用的就是最终的评测路径。

更进阶一点的做法是:准备多份不同思路的正确实现(比如暴力解和正解),一起跑,看它们在小数据上是否一致;再准备几份故意写错的代码,确认它们能在该被卡的点上被卡住。一套数据是不是好数据,很大程度上看它能不能稳定区分对的代码和错的代码。用 Arbiter 来做这件事,省心的地方在于配置一次就能反复复用。

7.2 保持评测机与正式赛环境一致

最后说一个容易被忽视但影响很大的点:评测机的环境和赛事规定要严格一致。编译器版本、运行库、时间基准,最好和正式环境保持同源。如果评测机用的是另一套环境,你在这里测出来的时间和正式赛可能有偏差,导致限制设得过紧或过松。

我的做法是固定一个"官方环境镜像",所有正式用途的评测都在这个环境里做,把它当成一把尺子。日常练习可以随意,但一旦要"算分",就切回这把尺子。这样出来的成绩才经得起追问,也方便换机器时原样复现。

我个人在实际操作中的体会是:Arbiter 这类工具真正的门槛不在界面,而在"数据摆法"和"参数设置"这两个看不见的地方。把目录组织成固定套路、加一题验一题、导出成绩后坚持抽样复核,这三件事做完,你基本就能避开九成以上的意外。剩下的坑,多半是编码和路径这类细枝末节,见一次记一次也就熟了。真遇到离谱结果时,别急着怀疑系统,先按第六节那条链条从头走一遍,你会发现大部分问题其实出在最不起眼的地方。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询