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 ~/contestX大写是有讲究的,它表示目录一定加执行位,普通文件只有在本来就是可执行的时候才加,不会误伤数据文件。
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 倍左右放宽限制来做流程验证,等确认逻辑都对了,再到标准环境里重新标定一次。这个做法不一定是最严谨的,但确实是省事又不容易出错的折中方案。
如果你已经把这套流程跑顺了,下一步可以往两个方向延伸。一是把选手源码的收集过程自动化,写个小脚本从共享目录里批量归档、重命名、建目录,省掉手工整理的一两个小时。二是把评测结果接进表格软件,自动算排名、生成奖状模板,一次组织几十人的比赛就不用手忙脚乱了。这两个方向都不复杂,值得一试。