2026年了,怎么在 Hydro 配置通信题?

测试功能,可能还有大量问题。

2026年了,怎么在 Hydro 配置通信题?

我们注意到 Hydro 题目中有这个选项:

好的怎么用呢?

我们发现,没有任何文档指出这个类型该怎么配置。

但在 Hydro 的 Github 仓库上,我们找到了一份代码:

import { NormalizedCase, STATUS } from '@hydrooj/common';
import { runFlow } from '../flow';
import { Parameter, runPiped } from '../sandbox';
import signals from '../signals';
import { Context, ContextSubTask } from './interface';

function judgeCase(c: NormalizedCase) {
    return async (ctx: Context, ctxSubtask: ContextSubTask) => {
        const { address_space_limit, process_limit } = ctx.session.getLang(ctx.lang);
        let managerArgs = '';
        const execute: Parameter[] = [{
            execute: ctx.executeManager.execute,
            stdin: c.input ? { src: c.input } : { content: '' },
            copyIn: ctx.executeManager.copyIn,
            time: c.time * 2,
            memory: c.memory * 2,
            env: { ...ctx.env, HYDRO_TESTCASE: c.id.toString() },
        }];
        const pipeMapping = [];
        for (let i = 0; i < ctx.config.num_processes; i++) {
            managerArgs += ` /proc/self/fd/${i * 2 + 3} /proc/self/fd/${i * 2 + 4}`;
            execute.push({
                execute: `${ctx.executeUser.execute} ${i}`,
                copyIn: ctx.executeUser.copyIn,
                time: c.time,
                memory: c.memory,
                addressSpaceLimit: address_space_limit,
                processLimit: process_limit,
            });
            pipeMapping.push({
                name: `sol2mgr[${i}]`,
                in: { index: i + 1, fd: 1 },
                out: { index: 0, fd: i * 2 + 3 },
            });
            pipeMapping.push({
                name: `mgr2sol[${i}]`,
                in: { index: 0, fd: i * 2 + 4 },
                out: { index: i + 1, fd: 0 },
            });
        }
        execute[0].execute += managerArgs;
        const res = await runPiped(execute, pipeMapping);
        const resManager = res[0];
        let time = 0;
        let memory = 0;
        let score = 0;
        let status = STATUS.STATUS_ACCEPTED;
        let message: any;
        for (let i = 0; i < ctx.config.num_processes; i++) {
            const result = res[i + 1];
            time += result.time;
            memory = Math.max(memory, result.memory);
            if (result.time > c.time) status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
            else if (result.memory > c.memory * 1024) status = STATUS.STATUS_MEMORY_LIMIT_EXCEEDED;
            else if ((result.code && result.code !== 13 /* Broken Pipe */) || (result.code === 13 && !resManager.code)) {
                status = STATUS.STATUS_RUNTIME_ERROR;
                if (ctx.config.detail === 'full') {
                    if (result.code < 32 && result.signalled) message = signals[result.code];
                    else message = { message: 'Your program returned {0}.', params: [result.code] };
                }
            }
        }
        if (status === STATUS.STATUS_ACCEPTED) {
            score = Math.floor(c.score * (+resManager.stdout || 0));
            status = score === c.score ? STATUS.STATUS_ACCEPTED : STATUS.STATUS_WRONG_ANSWER;
            message = resManager.stderr;
            if (resManager.code) message += ` (Manager exited with code ${resManager.code})`;
        }
        return {
            id: c.id,
            subtaskId: ctxSubtask.subtask.id,
            status,
            score,
            time,
            memory,
            message,
        };
    };
}

export const judge = async (ctx: Context) => await runFlow(ctx, {
    compile: async () => {
        [ctx.executeUser, ctx.executeManager] = await Promise.all([
            ctx.compile(ctx.lang, ctx.code),
            ctx.compileLocalFile('manager', ctx.config.manager),
        ]);
    },
    judgeCase,
});

当然很可能看不懂(我也看不懂),但在使用 AI 工具分析后,就知道 Hydro 如何评测通信题了:

  1. 你需要实现一个 manager,功能是管理通信和评分。
  2. Hydro 会将选手代码在 num_processes 个进程里独立运行,同时创建 num_processes*2 个管道,第 \(i\) 个进程的输入被重定向到管道 \(2i+2\),输出被重定向到是 \(2i+1\)
  3. Manager 可以使用管道与进程 IO 通信,最后输出 \([0,1]\) 间的实数表示分数,stderr 将输出评分信息,stdin 读入输入数据。暂时不知道怎么读入输出数据。(不过也没什么影响)

于是我们使用 __gnu_cxx::stdio_filebuf 来实现与进程 IO 通信,用法如下:

__gnu_cxx::stdio_filebuf<char> fin([PID_IN],std::ios::in),fout([PID_OUT],std::ios::out)

这里 [PID_IN] 表示向进程读取的管道编号,[PID_OUT] 表示向进程发送的管道编号。

使用前记得导入头文件 #include <ext/stdio_filebuf.h>

举例此题

compile.sh

g++ foo.cc grader.cc -o foo -O2

manager.cc

#include <ext/stdio_filebuf.h>
#include <iostream>
#include <cstring>
using namespace std;
int main(){
    __gnu_cxx::stdio_filebuf<char> bufain(3,ios::in),bufaout(4,ios::out),bufbin(5,ios::in),bufbout(6,ios::out);
    istream ain(&bufain);
    ostream aout(&bufaout);
    istream bin(&bufbin);
    ostream bout(&bufbout);
    string S,T;
    cin>>S>>T;
    aout<<"Alice\n";
    aout.flush();
    aout<<S;
    aout.flush();
    int x;
    ain>>x;
    if(x<0||x>=(1<<20)){
        cout<<"0";
        cerr<<"You Have No E!!!";
        return 0;
    }
    bout<<"Bob\n";
    bout.flush();
    bout<<T<<"\n";
    bout.flush();
    bout<<x;
    bout.flush();
    int res;
    bin>>res;
    if(res<0||res>S.size()){
        cout<<"0";
        cerr<<"You Have No Egg!!!";
        return 0;
    }
    if(res==0){
        if(S!=T){
            cout<<"0";
            cerr<<"Wrong Answer.";
        }
        else{
            cout<<"1";
            cerr<<"Accepted.";
        }
    }
    else if(S[res-1]!=T[res-1]){
        cout<<"1";
        cerr<<"Accepted.";
    }
    else{
        cout<<"0";
        cerr<<"Wrong Answer.";
    }
    return 0;
}

grader.cc

#include<iostream>
#include<cstring>
using namespace std;
int Alice(string S);
int Bob(string T,int x);
int main(){
    string op,str;
    cin>>op>>str;
    if(op=="Alice"){
        cout<<Alice(str);
    }
    else{
        int x;
        cin>>x;
        cout<<Bob(str,x);
    }
    return 0;
}
posted @ 2026-04-16 19:36  ThisIsLu  阅读(96)  评论(0)    收藏  举报