拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

NOI Linux 2.0 下用 Wine 部署 Arbiter 评测环境

NOI Linux 2.0 下用 Wine 部署 Arbiter 评测环境

1. 先把两个主角的角色分清楚

1.1 NOI Linux 2.0 这套环境究竟是什么

很多刚接触竞赛的同学会把 NOI Linux 2.0 当成一个"软件",其实它是一整套打包好的操作系统镜像。底层选的是 Ubuntu 20.04 LTS,内核版本常年停在 5.4 系列,好处是足够稳,坏处是很多新硬件驱动偏老,笔记本装实体机偶尔会卡在网卡或无线驱动上。它预装的工具链以竞赛需求为准:g++ 9.3.0、gcc、gdb、Free Pascal 3.0.4、Python 3.8,编辑器给了 vim、emacs、gedit、Code::Blocks、Geany 这几样,评测时用的时间限制、内存限制也都是按这套环境标定的。默认账户名一般为noilinux,登录后sudo是免密的,桌面上还有个"终端"快捷方式,日常操作基本都在终端里完成。

这套环境的核心价值不在于"好用",而在于"一致"。你在自己机器上跑出来的程序,和考场上跑出来的程序,编译参数、库版本、时间测量方式都一样,成绩才具备可比性。所以哪怕是平时练题,只要想认真模拟一次比赛,就应该在这套环境里跑,而不是在 Windows 上用 Dev-C++ 随便点一下运行。

1.2 Arbiter 在评测链路里站的位置

Arbiter 是 Windows 平台上的一个图形化评测程序,用 .NET 写的,界面是典型的 WinForms 风格。它干的事情说白了就四件:管理选手名单、管理试题数据、批量编译并运行选手源码、按规则比对输出并生成成绩单。和它同类的还有 Lemon、Cena,功能上大同小异,区别主要在比较器的可扩展性和界面习惯上。

需要提前说清楚的一点是:Arbiter 本身是 Windows 程序,它并不"属于"NOI Linux。我们这篇要做的,是在 NOI Linux 2.0 里把 Arbiter 跑起来,也就是用 Wine 当成 Windows 运行层,再给 Arbiter 配一套能用的 Windows 版编译器。这条路能走通,但中间有几个必须提前知道的坑,后面会逐个讲透。

提示:如果你只是想在 NOI Linux 里验证自己写的程序对不对,其实不需要 Arbiter,直接g++ -O2 -o a a.cpp再手动对拍就够了,速度还更快。Arbiter 真正的价值在于"批量",比如一个班几十个选手、四五道题、几百个测试点,手工跑会崩溃,这时候它才值得折腾。

1.3 什么场景下适合走这套组合

我给三类人推荐这套方案。第一类是学校的信息学教练或者集训队组织者,需要收上来一批选手的源码然后一次性跑完出成绩单,这种情况 Arbiter 的选手管理功能能省下大量时间。第二类是想完整模拟一场比赛流程的自学者,包括赛前怎么整理数据、赛中怎么收源码、赛后怎么出成绩,这套流程走一遍会理解很多平时注意不到的细节。第三类是赛事运维相关的从业者,需要在 Linux 服务器上做评测环境的一致性验证。

反过来,如果只是刷一两道题,或者只是想知道"我这题能过几个点",那完全不必要。搞清楚适用边界比盲目上手更重要,这也是我在多年使用过程中最想对新手说的一句话。


2. 环境搭建:把 Arbiter 在 NOI Linux 2.0 里跑起来

2.1 先把 NOI Linux 2.0 装好并做基础自检

安装方式有两种,实体机和虚拟机我都试过,作为日常练题我推荐虚拟机,因为折腾坏了直接回滚快照,不用重装系统。虚拟机里内存建议给到 4GB 以上,CPU 至少两核,硬盘 30GB 起。安装过程本身很标准,一路下一步即可,唯一要注意的是语言和键盘布局,选中文环境能省掉后面打字的麻烦。

装完之后别急着装 Arbiter,先花三分钟自检,确认基础工具链是正常的:

g++ --version # 确认 g++ 9.3.0 存在 free -h # 确认可用内存 ulimit -a # 看栈空间、文件句柄限制 df -h # 确认根分区剩余空间

这四条命令看起来简单,却能提前暴露 80% 的"跑评测跑不起来"的问题。比如ulimit -s如果是 8192,就意味着选手程序在深度递归时更容易爆栈,和考场的标准环境不一致;再比如根分区只剩 2GB,评测跑一半写日志写满磁盘,成绩直接作废。这些都不是危言耸听,我本人就踩过一次磁盘写满导致结果文件残缺的坑,排查了整整一个晚上。

自检完之后,建议先把系统更新一遍,sudo apt update && sudo apt upgrade -y。不要担心版本升级破坏环境一致性,NOI Linux 2.0 的源里 g++ 就是 9.3.0,不会跳到 10 或者 11,这一点是安全的。

2.2 用 Wine 把 Windows 运行层架起来

Wine 是 Linux 上运行 Windows 程序的一种兼容层,它把 Windows 的系统调用翻译成 Linux 的,所以不需要装完整的 Windows 系统。Ubuntu 20.04 的官方源里就有 wine,版本是 5.0 系列,够用了。

安装步骤:

sudo dpkg --add-architecture i386 sudo apt update sudo apt install -y wine64 wine32 winetricks

第一行是开启 32 位架构支持,有些老版本的 .NET 程序是 32 位的,不装这个会直接报"不是有效的 Win32 程序"。装完之后用wine --version确认一下,能看到版本号就说明通了。

接下来这一步很多人会忽略,但非常关键:中文字体。Wine 默认环境下中文会显示成一堆方块或者乱码,Arbiter 界面上选手名字全是问号,根本没法看。解决办法有两个,一个是sudo apt install fonts-wine,另一个是把系统里的中文字体拷进 Wine 的字体目录:

cp /usr/share/fonts/truetype/*/NotoSansCJK*.ttc ~/.wine/drive_c/windows/Fonts/

如果系统里没有 Noto 系列中文字体,先sudo apt install fonts-noto-cjk装上再拷。拷完之后最好重启一次 Wine 前缀(wineserver -k),让字体缓存生效。

注意:不要在这个环节尝试联网下载各种"增强包",很多第三方脚本会改动系统级的库,把你原本干净的 NOI Linux 环境搞乱,后面再想复现考场环境就难了。能用系统源解决的,就不要走别的路。

2.3 给 Wine 配一套 Windows 版编译器

这是整条路线里最容易翻车的地方,必须讲透原理。

Arbiter 在评测时会去调用编译器,默认配置一般是g++。在 Windows 上,它会去 PATH 里找一个叫g++.exe的东西。但在 Linux 里,我们的g++是 ELF 格式的可执行文件,Wine 是没法直接运行 ELF 的,它只会去找 PE 格式的.exe。结果就是:点下评测按钮,界面转两圈,然后一片"编译错误",或者干脆报"找不到文件"。

解法只有一个:给 Wine 的 C 盘里放一套 Windows 版的 MinGW-w64 编译器。

具体做法是,在一台有网络的环境里提前准备好 mingw-w64 的压缩包,选i686或x86_64都行,解压到 Wine 的 C 盘目录下:

mkdir -p ~/.wine/drive_c/mingw64 # 假设已经拿到 mingw64 压缩包 tar -xf mingw-w64.tar.xz -C ~/.wine/drive_c/mingw64 --strip-components=1

然后验证一下 Wine 能不能找到它:

WINEPREFIX=~/.wine wine C:\\mingw64\\bin\\g++.exe --version

能打出g++ (MinGW-W64 ...) 8.x.x之类的版本号,就说明编译器通了。这一步过了,后面的路才走得下去。

这里有个绕不开的取舍需要说明:用 MinGW 编译出来的是 Windows PE 程序,跑在 Wine 里,时间测量会比原生 Linux 慢一些,判 TLE 的边界会漂。所以这套方案适合"流程演练"和"结果正确性验证",如果要做正式成绩认定,还是建议在 Windows 环境里跑 Arbiter,或者用 NOI Linux 原生的评测脚本重新标定时间限制。

2.4 部署 Arbiter 并完成首次启动

编译器通了之后,把 Arbiter 的程序目录整个拷到 Wine 的 C 盘里,比如~/.wine/drive_c/Arbiter/。不要放在 Linux 路径下直接双击运行,那样路径里带Z:\前缀,程序读取相对路径的配置文件时容易出问题。

启动命令:

cd ~/.wine/drive_c/Arbiter WINEPREFIX=~/.wine wine Arbiter.exe

第一次启动可能会弹一个 .NET 运行时的提示。Arbiter 依赖 .NET Framework,Wine 里自带的 mono 有时能顶住,有时不行。如果弹窗报缺少组件,用winetricks dotnet48补一下,这个过程比较慢,中途可能弹好几个安装窗口,按提示点就行,别中途关掉。装完之后wineserver -k重启一次,再启动 Arbiter。

启动成功后,主界面一般会分成几个区域:上方是菜单和工具栏,左侧或者上方有选手列表和试题列表,右边是操作区。不同版本的 Arbiter 界面布局略有差别,但核心操作就那几个,后面按流程走。

实操心得:给 Arbiter 建一个专用的 Wine 前缀,比如WINEPREFIX=~/.arbiter-wine,不要和系统默认前缀混用。这样万一某个前缀被折腾坏了,直接删目录重建,不影响其他 Wine 程序,反之亦然。这个习惯能帮你省下大量重复折腾的时间。


3. 目录结构与数据规范:评测能不能跑,八成就看这一步

3.1 整场比赛的落地目录怎么摆

Arbiter 对目录结构是有约定的,摆错了它认不出来。我习惯用下面这种布局,经过多次实践是最稳的:

~/contest/ ├── players.txt # 选手名单,一行一个名字 ├── data/ # 所有试题的数据 │ ├── perm/ # 题目一 │ │ ├── 1.in │ │ ├── 1.out │ │ ├── 2.in │ │ └── 2.out │ └── game/ # 题目二 │ ├── 1.in │ └── 1.out ├── source/ # 选手源码,一人一个文件夹 │ ├── zhangsan/ │ │ ├── perm.cpp │ │ └── game.cpp │ └── lisi/ │ ├── perm.cpp │ └── game.cpp └── result/ # 评测结果输出目录

三块内容各司其职:data放数据和标准答案,source放选手提交,result留给 Arbiter 写结果文件。注意source下面每个选手一个文件夹,文件夹名必须和players.txt里的名字完全一致,一个字符都不能差。中文名和英文名都能用,但强烈建议全用英文或者全用中文,不要混着来,之前遇到过一个选手叫"张三",名单里写成zhangsan,结果提交被判成"未提交",白忙活一场。

另外要提醒的是,Linux 的文件名区分大小写,Perm.cpp和perm.cpp是两个文件。Windows 上不区分,所以从 Windows 拷过来的文件在这里可能出问题。这一点在后面讲源码命名时还会再强调一次。

3.2 单道题的数据文件怎么命名与准备

数据文件的命名,Arbiter 默认按编号配对的模式,也就是1.in配1.out,2.in配2.out,依此类推。编号必须从 1 开始连续,中间不能跳号,跳了会导致后面的数据全部读不到。

需要特别留意的是扩展名。有的评测系统习惯用.ans作为答案文件,Arbiter 默认用的是.out。如果你的题目数据是从别处拿来的、用的是.ans,要么在题目设置里手动改配对规则,要么批量重命名:

cd data/perm for f in *.ans; do mv "$f" "${f%.ans}.out"; done

数据内容本身也有讲究。输入文件末尾不要留多余的空行,虽然多数比较器会忽略行尾空白,但有些严格模式会判错。输出文件的最后一个换行可以有,这是标准做法。另外,文件编码统一用无 BOM 的 UTF-8,带 BOM 的文件在读取时会在开头多出三个不可见字节,程序读进来的第一个数字就变成乱码了,这是相当隐蔽的坑。

数据本身的规模也要检查。有个简单的体检方法:

wc -l data/perm/*.in | tail -1 # 看总行数 du -sh data/perm/ # 看数据总大小

如果单道题的数据超过 200MB,评测过程会明显变慢,写结果也会占空间,可能要考虑精简。如果1.in是空的(0 字节),那基本可以确定数据整理出了问题,赶紧回头检查。

注意:准备数据的时候,最好随手拿一个"明显正确"的参考程序跑一遍,确认它能拿到满分。这是最省事的自检方式,比对着数据一个个看快得多。我见过太多次数据本身出错、最后判了一整场错案的情况。

3.3 选手名单与源码文件的命名规则

players.txt里的名字决定了源文件夹的查找路径。格式很简单,一行一个,不要有多余空格,不要有空行,编码同样是无 BOM 的 UTF-8。在 Linux 下可以用cat -A players.txt检查,如果每行末尾显示的是$就对了,如果显示^M$说明是从 Windows 拷过来的 CRLF 换行,需要转换:

sed -i 's/\r$//' players.txt

源码文件的命名,规则是"选手名文件夹 + 题目名文件"。比如张三提交的perm题,路径应该是source/zhangsan/perm.cpp。这里有两层匹配:文件夹名要匹配名单,文件名要匹配题目名,两个都不对上,Arbiter 就会认为该选手这题没交。

关于扩展名,.cpp是通用的。有些选手会写成.cc、.cxx,虽然理论上 g++ 都认,但 Arbiter 的文件扫描是按扩展名过滤的,可能扫不到。最稳的办法是统一要求所有人用.cpp,并在赛前说明里写清楚。

还有一个隐蔽的坑:文件名的大小写。如果题目在 Arbiter 里登记的名字是perm,选手提交的文件叫Perm.cpp,在 Linux 上就是找不到。这个错误在 Windows 上完全不会出现,所以从 Windows 迁移过来的选手特别容易中招。解决办法是评测前写一个小脚本统一检查:

for d in source/*/; do for f in "$d"*.cpp; do base=$(basename "$f" .cpp) name=$(basename "$d") echo "$name -> $base" done done

把输出和题目名单对一遍,几分钟就能把这类问题清干净,比起跑完整场再发现要划算太多。


4. 界面实操:从新建比赛到拿到成绩单

4.1 新建比赛与批量导入选手

启动 Arbiter 之后,第一件事是新建一个比赛。菜单里找"文件"或者左上角的"新建"按钮,选择刚才规划的~/contest作为工作目录。程序会在里面生成自己的配置文件和结果目录,原来的data和source一般不会被破坏,但保险起见,操作前先备份一份。

接下来是导入选手。Arbiter 支持两种方式,一种是在选手列表里右键逐个添加,另一种是"从文件导入"。选手多的时候当然用后者,把players.txt路径填进去,确认编码选 UTF-8,点导入。导入完成后一定要扫一眼列表,看看人名有没有变成问号或者乱码。如果出现了,说明编码没对上,回退重来,不要硬着头皮往下走,否则后面编译时找不到文件夹,报的错会让你一脸懵。

导入之后还有一个细节:确认每个选手的源码目录被正确关联。有的版本会默认把source目录作为源码根,有的版本需要你手动指定。这个设置一般藏在选手列表的属性里,翻一下就有了。

实操心得:选手名单导入之后,建议先拿一两个选手做"试跑",确认路径关联没问题,再批量放开。整场跑一次可能要十几分钟,如果路径错了,这十几分钟就是白等。小步验证,是评测运维里最值钱的习惯。

4.2 添加试题并做一次数据体检

试题的添加类似,在试题列表里选择"添加试题",输入题目名称(必须和源文件里用的名字完全一致),然后指定数据目录。指定完之后,Arbiter 会扫描目录里的.in和.out配对,界面上一般会显示"找到 N 个测试点"。

这里有两个地方要盯紧。第一是测试点数量对不对,说好 10 个点结果只识别出 8 个,说明命名或者编号有问题。第二是看一眼每个测试点的输入输出是不是都非空,有些版本会在列表里显示文件大小,为空的一眼就能看出来。

题目属性里还有几个必填项:时间限制、内存限制、比较方式、源代码文件名规则。时间限制填毫秒数,比如 1000 表示 1 秒。内存限制填 MB。比较方式默认是"忽略行尾空白"的文本比较,如果题目有特殊要求(比如浮点数允许误差、或者多解),需要换成自定义比较器,这部分留到第 5 节讲。

源代码文件名规则这一项容易被忽略。有的版本默认只找和题目同名的.cpp,有的版本允许配置。如果你的题目叫perm但选手提交的是permutation.cpp,那就要么改规则,要么让选手改名。统一要求文件名等于题目名,是最省事的做法。

全部配置好之后,界面上应该能看到一个矩阵:行是选手,列是题目,格子里显示"待评测"或者类似状态。到这一步,环境、数据、名单、源码四样东西就都挂上了。

4.3 跑评测与读懂结果

点击"开始评测"之前,最后确认三件事:编译器的路径配置对不对、结果输出目录有没有写权限、磁盘空间够不够。这三条任何一条出问题,都会让评测跑到一半崩掉。

评测过程中,界面会实时刷新每个测试点的状态。常见状态有这么几种:正确(通常显示绿色或者AC)、答案错误(WA)、超出时间(TLE)、超出内存(MLE)、运行时错误(RE,包括段错误、除零、异常退出)、编译错误(CE)。

评测结束后,Arbiter 会在结果目录里生成成绩文件,常见格式是.csv或者.txt,内容是按选手和题目展开的分数表,有的还会附上每个测试点的耗时和内存占用。拿到成绩后,第一件事不是发出去,而是抽查几个结果,从对应的源文件里翻出代码,人工看一眼逻辑,确认分数对得上。这个动作叫"复查",是评测流程里不能省的一步。

结果的解读也有讲究。如果一个选手所有测试点都是RE,大概率是读文件路径写错了,比如用了绝对路径;如果所有点都是TLE,可能是死循环,也可能是文件读入方式太慢,用了cin没关同步;如果前几个点对、后几个点错,那通常是算法问题,小数据能过大数据挂掉,这种情况不用怀疑评测系统,直接看算法。

提示:评测跑完之后,别急着删中间产物。编译日志、运行日志这些东西,是排查争议的唯一依据。有选手对成绩有疑问时,能拿出编译日志和运行日志,沟通成本会低很多。建议把这些文件按日期归档,保留至少一个赛季。


5. 评测规则进阶:比较器与特殊题型

5.1 默认比较器到底在做什么

Arbiter 的默认比较逻辑,说起来就三条规则:逐行比较、忽略行尾空白(包括行尾空格、制表符和\r)、忽略文件末尾多余的空行。符合这三条的差异不算错,超出这三条的差异一律判WA。

这三条规则看起来宽松,实际上能挡掉很多问题。比如选手用 Windows 写的输出带了\r\n,Linux 判题程序按\n分割,如果不忽略\r,每一行都会多一个字符,全盘WA。再比如有些程序的输出末尾多打一个空行,严格比较也会挂。所以这个默认规则是保护选手的。

但反过来,如果题目要求精确输出,比如输出一个字符串要求逐字符一致,那默认比较器的宽松规则可能就不合适了。这时候需要换比较方式。

5.2 特殊题型怎么接

评测里最容易出问题的是三类题:浮点数输出、多解题、交互题。

浮点数题,标准答案是3.1415926535,选手输出3.1415926536,默认比较器会判错。这时候需要换成"带误差的浮点比较",通常做法是用自定义比较器,按相对误差或绝对误差判断。误差值怎么定,要看题目要求,常见的是1e-6或者1e-9。

多解题是指答案不唯一,只要满足题目条件都算对。比如图论里要求输出任意一条合法路径。这类题默认比较器无能为力,必须写自定义校验程序。思路是:读入测试点输入、选手输出、标准输出,然后自己写逻辑验证选手输出的合法性,返回"正确"或者"错误"。

交互题的复杂度更高,选手程序需要在运行过程中和评测程序通信。Arbiter 支持这类题,但配置起来相对繁琐,需要指定交互程序,还要处理管道通信。这类题在平时练习中很少用到,如果遇到,建议先把通信机制想清楚,再动手配。

需要说明的是,Arbiter 的自定义比较器用的是 .NET 脚本,语法接近 C#,对于只会 C++ 的同学来说有额外的学习成本。如果题目本身不复杂,一个更轻量的替代思路是:先用 Arbiter 跑一遍默认比较,把明显错的筛掉,剩下的模糊情况用自己写的 Python 脚本手动复查。这个"两步走"的策略在数据量不大的时候非常好用。


6. 踩坑实录与常见问题速查

6.1 编码、换行、权限这三大经典坑

编码问题排第一。从 Windows 拷过来的文本文件,默认可能是 GBK 编码,或者带 BOM 的 UTF-8,这两种在 Linux 下都会出问题。检查方法:

file -i players.txt data/perm/1.in

输出里如果带charset=iso-8859-1或者charset=unknown-8bit,基本就是 GBK,需要转成 UTF-8:

iconv -f GBK -t UTF-8 players.txt -o players_utf8.txt

带 BOM 的可以这样去掉:

sed -i '1s/^\xEF\xBB\xBF//' players.txt

换行符问题排第二,前面提过,CRLF 转 LF 用sed -i 's/\r$//'就行,批量处理可以加find。

权限问题排第三。从 U 盘或者共享文件夹拷进来的文件,权限位可能全乱,导致 Arbiter 读不到或者运行不了。目录需要可读可执行,文件需要可读。一条命令统一修:

chmod -R u+rwX,go+rX ~/contest

X大写是有讲究的,它表示目录一定加执行位,普通文件只有在本来就是可执行的时候才加,不会误伤数据文件。

6.2 评测结果异常时的排查思路

结果异常分几种情况,处理方式不一样。

全盘"未提交",先查选手名单和源文件名的匹配关系,多半是名字对不上。全盘编译错误,先看编译器路径配置,再手动拿一份源码跑一次g++,确认编译器本身没问题。全盘超时,先确认时间限制单位填的是毫秒还是秒,这是最常见的低级错误。部分测试点运行时错误,看是不是内存越界或者数据规模问题,随机数据的题目尤其容易在前面几个点正常、后面崩。

还有一类异常是"分数看着不对,但没明显报错"。这种情况优先怀疑数据本身,拿一份公认正确的参考程序跑一遍,看它能不能满分。如果参考程序都拿不到满分,那就是数据或者评测配置的问题,跟选手无关。

排查的时候有个技巧:永远从最小的可复现单元开始。不要一上来就跑整场,先跑单个选手单道题,缩小范围,很快就能定位。整场跑一遍十几分钟,单题跑一下几秒钟,差距是几十倍。

6.3 常见问题速查表

现象可能原因处理办法
界面中文显示为方块Wine 未配置中文字体拷字体到 Wine 的 Fonts 目录并重启前缀
点评测后立即全盘编译错误Wine 里找不到 Windows 版 g++配置 MinGW-w64 并在 Arbiter 中指定路径
全部选手都显示"未提交"名单与源文件夹名不匹配逐字比对,注意大小写和空格
只识别出部分测试点编号不连续或扩展名不一致重新编号,统一改为.in/.out
每行输出都被判错换行符是 CRLF批量转换换行符
输入数据开头异常字符文件带 BOM去掉 BOM
结果目录写不进去权限不足用chmod修目录权限
评测速度明显偏慢Wine 时间测量开销适当放宽时间限制,仅用于流程演练

实操心得:把上面这张表打印出来贴在显示器边上,能省下不少查资料的时间。我带了几年集训队,发现新手出问题基本都逃不出这几条,重复讲不如让他们自己对着表排查,动手一次比听十遍记得牢。


7. 一点个人体会和后续可以延伸的方向

整套流程我自己反复走过很多次,从最早的实体机装 NOI Linux,到后来虚拟机快照一把梭,再到用 Wine 把 Arbiter 拉起来跑,每一次踩的坑都不太一样,但规律是相通的:问题几乎从不出在"核心逻辑"上,而是出在环境、编码、路径这些看起来最不重要的地方。所以我现在组织任何一次评测,都会先花十分钟做那四项基础自检,再花五分钟拿一个选手试跑,宁可在开头慢一点,也不要在结尾返工。

还有个经验想分享:Wine 下跑 Arbiter 的时间测量确实不如原生准确,所以对于时间限制卡得很紧的题目,我一般会按 1.5 倍左右放宽限制来做流程验证,等确认逻辑都对了,再到标准环境里重新标定一次。这个做法不一定是最严谨的,但确实是省事又不容易出错的折中方案。

如果你已经把这套流程跑顺了,下一步可以往两个方向延伸。一是把选手源码的收集过程自动化,写个小脚本从共享目录里批量归档、重命名、建目录,省掉手工整理的一两个小时。二是把评测结果接进表格软件,自动算排名、生成奖状模板,一次组织几十人的比赛就不用手忙脚乱了。这两个方向都不复杂,值得一试。

返回列表