Hydro SPJ 进阶:评测机会说人话了!

0. 故事背景

众所周知,一场比赛需要一张头图

(如果你觉得这张图中二得要命那就尽管骂吧)

但是,注意到这张图实际上是Hydro的评测结果页面P出来的。有没有感觉Wrong Answer后面那些黑字过于自然了?——没错,我的评测机成精了(bushi


Hydro SPJ 进阶:评测机会说人话了!

上次我们在【告别答案唯一!】中讲了SPJ的基本用法。但是,你有没有想过——让评测机在 WA 的时候,给选手一句带中文的提示?

比如选手输出错了,你可以告诉他:

  • “第 3 个测试点:输出格式有误,请检查是否有多余空格”
  • “第 5 个测试点:数值超出范围 [1, 10]”
  • “第 7 个测试点:你的程序跑得太慢了,但还没超时——再优化一下吧”

这不光能让选手更快定位问题,还能让评测体验变得更有“人情味”。今天就来聊聊怎么在 Hydro 的 SPJ 里输出中文消息。


1. 一个简单的开始

其实在 testlib 里输出中文,比你想象的要简单。直接往 quitf() 里写中文就行:

#include "testlib.h"

int main(int argc, char* argv[]) {
    registerTestlibCmd(argc, argv);

    int user = ouf.readInt();
    int answer = ans.readInt();

    if (user != answer) {
        quitf(_wa, "答案错误:期望 %d,实际 %d", answer, user);
    }

    quitf(_ok, "回答正确!答案是 %d", answer);
}

就这么简单。quitf() 的字符串参数支持 UTF-8 中文,Hydro 的评测页面会原样显示出来。

但是,如果只是这样,每个 WA 点显示的消息都一样,还是不够好玩。能不能让不同的测试点显示不同的提示?


2. 核心技巧:获取当前测试点编号

Hydro 在运行 SPJ 时,会通过环境变量 HYDRO_TESTCASE 把当前测试点的编号传进来。我们可以在 checker.cc 里读取这个变量:

#include "testlib.h"
#include <cstdlib>  // for getenv
#include <string>

int main(int argc, char* argv[]) {
    registerTestlibCmd(argc, argv);

    // 获取当前测试点编号
    const char* testcase_str = std::getenv("HYDRO_TESTCASE");
    int testcase_id = 0;
    if (testcase_str != nullptr) {
        testcase_id = std::atoi(testcase_str);
    }

    // 读取选手输出和标准答案
    int user = ouf.readInt();
    int answer = ans.readInt();

    if (user != answer) {
        // 根据测试点编号输出不同的中文提示
        switch (testcase_id) {
            case 1:
                quitf(_wa, "第 1 个点 WA:检查一下是不是忘了处理负数?");
                break;
            case 2:
                quitf(_wa, "第 2 个点 WA:输入的两个数可能很大,用 int 够吗?");
                break;
            case 3:
                quitf(_wa, "第 3 个点 WA:输出格式有误,检查是否有多余空格或换行");
                break;
            case 4:
                quitf(_wa, "第 4 个点 WA:你的程序好像把加法算成了减法");
                break;
            case 5:
                quitf(_wa, "第 5 个点 WA:边界情况没处理好,试试 n=0 或 n=1");
                break;
            default:
                quitf(_wa, "答案错误:期望 %d,实际 %d", answer, user);
        }
    }

    quitf(_ok, "第 %d 个点通过 ✅", testcase_id);
}

这样,选手在评测结果页面看到的就是带中文的、针对性的错误提示,而不是千篇一律的 Wrong Answer。

3. 更实用的场景

除了“整活”,这种技巧在实际出题中也有很多用处:

场景一:分阶段提示

如果你的题目有多个 Subtask,可以在不同的测试点给出不同的反馈:

if (testcase_id <= 3) {
    quitf(_wa, "小数据点 WA,检查基本逻辑");
} else if (testcase_id <= 6) {
    quitf(_wa, "中数据点 WA,可能是时间复杂度太高");
} else {
    quitf(_wa, "大数据点 WA,检查是否溢出或超时");
}

场景二:输出格式检查

// 假设选手应该输出 n 个数
for (int i = 0; i < n; i++) {
    if (ouf.seekEof()) {
        quitf(_wa, "第 %d 个测试点:输出数量不足,期望 %d 个数", testcase_id, n);
    }
    // ... 读取并检查
}

场景三:部分分提示

结合 quitp() 给部分分时,也可以加上中文说明:

if ( /*部分正确*/ ) {
    quitp(50, "部分正确:输出格式没问题,但数值有误,给一半分");
}

(等会儿?做慈善呢?数值有误还判对?)

4. 注意事项

① 文件编码

checker.cc 文件本身要保存为 UTF-8 编码,否则中文会变成乱码。大多数现代编辑器(VS Code、Sublime等)默认都是 UTF-8,直接用就行。

② 环境变量是否存在

HYDRO_TESTCASE 是 Hydro 特有的环境变量,在本地调试时可能不存在。所以代码里要判断指针是否为空:

const char* testcase_str = std::getenv("HYDRO_TESTCASE");
if (testcase_str != nullptr) {
    testcase_id = std::atoi(testcase_str);
}

③ 不要用 cout / printf

在 SPJ 里不能用 cout 或 printf 直接输出。所有给选手看的消息都必须通过 quitf()、quitp() 或 quit() 系列函数返回。

④ 消息长度

虽然理论上没有严格限制,但评测页面显示区域有限,建议消息不要太长,控制在 50 个字以内效果最好。

5. 完整示例

下面是一个完整的 checker.cc,它结合了之前文章里 A+B 的例子,给每个测试点定制了不同的中文 WA 提示:

#include "testlib.h"
#include <cstdlib>
#include <string>

int main(int argc, char* argv[]) {
    setName("A+B with Chinese hints");
    registerTestlibCmd(argc, argv);

    // 获取测试点编号
    const char* testcase_str = std::getenv("HYDRO_TESTCASE");
    int testcase_id = 0;
    if (testcase_str != nullptr) {
        testcase_id = std::atoi(testcase_str);
    }

    int answer = ans.readInt();
    int user = ouf.readInt();

    // 特殊整活:输出 114514 给 50% 分数
    if (user == 114514) {
        quitp(50, "你输出了 114514,太搞了,给你一半分吧 🤣");
    }

    if (user != answer) {
        // 根据测试点给出不同的中文提示
        const char* hints[] = {
            "检查一下是不是忘了处理负数?",
            "输入的两个数可能很大,用 int 够吗?",
            "输出格式有误,检查是否有多余空格",
            "你的程序好像把加法算成了减法",
            "边界情况没处理好,试试 n=0 或 n=1",
            "这个点专门测大数,用 long long 了吗?",
            "你的程序输出为空,检查一下读入",
            "这个点数据比较极限,再想想算法",
            "离 AC 就差一步了,检查一下符号",
            "最后一个点了,冲鸭!检查所有细节"
        };
        int idx = (testcase_id - 1) % 10;
        quitf(_wa, "第 %d 个点 WA:%s", testcase_id, hints[idx]);
    }

    quitf(_ok, "第 %d 个点通过!答案是 %d ✅", testcase_id, answer);
}

6. 结语

让评测机“说人话”其实就这么简单——核心就两步:

  1. 用 getenv("HYDRO_TESTCASE") 拿到当前测试点编号;
  2. 用 quitf() 输出带中文的提示信息。

这个小技巧能让 评测机成精 你的题目评测体验上升一个档次。选手不再面对冷冰冰的 WA,而是能收到有针对性的、带中文的提示——对新手尤其友好。

下次出题的时候,不妨试试给你的 SPJ 加点“人话”吧

posted @ 2026-08-15 22:59  Void__114  阅读(32)  评论(0)    收藏  举报