信息学竞赛对拍技巧综合报告 v3

作者:综合整理自 OI Wiki、洛谷、博客园等社区
版本:v3
日期:2026-06-14
定位:面向初学者的对拍入门与实战指南

第0章 五分钟极速上手(复制即跑)

🚀 第一次对拍?照着做,5分钟跑通!

只需准备 3 个 .cpp 文件,编译后运行 duipai.exe 即可。

步骤一:准备 3 个程序

题目示例:输入 n 和 n 个整数,输出最大值。

① my.cpp(待测程序,故意留一个 bug)
#include <bits/stdc++.h>
using namespace std;
int main() {
    int n; cin >> n;
    int ans = 0;           // bug:全负数时输出 0
    for (int i = 0; i < n; i++) {
        int x; cin >> x;
        ans = max(ans, x);
    }
    cout << ans << endl;
    return 0;
}
② std.cpp(暴力程序,保证正确)
#include <bits/stdc++.h>
using namespace std;
int main() {
    int n; cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; i++) cin >> a[i];
    int ans = a[0];
    for (int i = 1; i < n; i++) ans = max(ans, a[i]);
    cout << ans << endl;
    return 0;
}
③ gen.cpp(数据生成器,先用这个最简版跑通)
#include <bits/stdc++.h>
using namespace std;
int main() {
    srand(time(0));
    int n = rand() % 3 + 1;   // 小数据,方便 std 快速跑完
    cout << n << endl;
    for (int i = 0; i < n; i++) {
        cout << rand() % 20 - 15  // 大概率出负数,方便触发 bug << " \n"[i == n - 1];
    }
    return 0;
}

步骤二:编写对拍总控程序

④ duipai.cpp(对拍主程序)
#include <cstdlib>
#include <cstdio>
int main() {
    int T = 0;
    while (true) {
        T++;
        system("gen.exe > data.in");
        system("std.exe < data.in > std.out");
        system("my.exe < data.in > my.out");
        // fc 返回 0 表示相同,非 0 表示不同
        if (system("fc my.out std.out > nul") != 0) {
            printf("===== WA at test #%d =====\n", T);
            return 0;
        } else {
            printf("Case #%d AC\n", T);
        }
    }
}

步骤三:编译并运行

💡 使用 Dev-C++ / Code::Blocks 的同学看这里
如果你不习惯用命令行,完全可以不用敲 g++ 命令!只需在 IDE 里依次打开 my.cppstd.cppgen.cppduipai.cpp 四个文件,分别点击一次"编译"(Dev-C++ 按 F9,Code::Blocks 按 F9 或 F11),生成好对应的 .exe 文件后,最后单独运行 duipai.cpp(或双击 duipai.exe)即可,效果完全一样。
g++ my.cpp -o my.exe -O2 -std=c++14
g++ std.cpp -o std.exe -O2 -std=c++14
g++ gen.cpp -o gen.exe -O2 -std=c++14
g++ duipai.cpp -o duipai.exe -O2 -std=c++14
duipai.exe
✅ 能跑通上面的例子,你就已经学会对拍了! 后面的章节会详细解释每一步的原理,并介绍更高级的技巧。

第1章 角色设定与核心概念

对拍涉及几个"角色",先用比喻理解它们,后面所有操作都会清晰很多。

🎯

my(考生)

你写的可能翻车的程序。速度快但可能有 bug。对拍的目的就是找出它的错误。

my.cpp
📚

std(标准答案)

暴力但绝对正确的程序。速度慢但答案一定对。是对拍的"裁判"。

std.cpp
🎲

gen(考官)

数据生成器。负责源源不断地出"卷子"(随机输入数据),让两个程序分别作答。

gen.cpp
💡 核心思想(一句话版)
让 gen 不断出随机数据,my 和 std 分别作答,比答案。答案不同→my 有 bug;答案相同→继续出下一组数据。
术语含义备注
对拍 / Stress Testing通过大量随机数据验证程序正确性名称来源于"两两比对输出"
暴力程序 / BF时间复杂度高但逻辑简单的正确程序通常指 std.cpp
正解 / 优化解你写的最终提交程序通常指 my.cpp
数据生成器 / Generator按题目格式生成合法随机输入的程序对拍质量的关键
Special Judge / SPJ答案不唯一时,用来判断输出是否合法的程序进阶内容,见附录D
TestlibCodeforces 出题库,含随机数、SPJ 等工具进阶内容,见附录F

第2章 对拍的基本原理

对拍自动化循环流程

gen 出题
my 作答
+
std 作答
比对答案
相同?继续 → 不同?停止

详细步骤:

  1. 出题:运行 gen.exe,将随机输入写入 data.in
  2. 作答:分别运行 my.exe 和 std.exe,从 data.in 读入,输出到 my.out 和 std.out
  3. 比对:比较两个 .out 文件内容是否完全相同
  4. 判定:相同 → 回到步骤1继续;不同 → 停止,保存 data.in 用于调试
⚠️ 两个绝对前提
  1. std 必须正确。如果 std 本身有 bug,对拍毫无意义——你会拿着错误答案去校验另一个错误答案。
  2. gen 每次数据必须不同。如果每次生成相同数据,对拍只验证了一组数据,等于没拍。
🤔 什么时候用对拍?

第3章 C++ 对拍完整实战(主线教程)

为什么主推 C++ 对拍?因为你已经在用 C++ 写算法了,不需要额外学批处理或 Shell 语法。在 Dev-C++ 或 VS Code 里一键编译运行即可,和平时写题完全一样。

3.1 准备三个程序(详细拆解)

每个程序的作用、写法、注意事项,逐一说明:

my.cpp(待测程序)—— 写你平时的解法即可

唯一要注意:不要写 freopen。用对拍时,输入/输出通过命令行重定向完成,代码中无需文件操作。

// ❌ 不要这样写(对拍时)
freopen("data.in", "r", stdin);
freopen("my.out", "w", stdout);

// ✅ 这样写,平时和对拍都能用
int main() {
    // 正常读入即可,无需 freopen
}

std.cpp(暴力程序)—— 核心是保证正确

暴力程序不要求效率,但逻辑必须简单、直接、无优化。最好是另一种思路实现,避免和 my 犯同样的错误。

题目类型std 写法建议
最值问题O(n²) 枚举所有子区间 / 子序列
图论最短路Floyd O(n³) 或 暴力 BFS
数论问题按定义暴力枚举
DP 问题暴力搜索 / 回溯(指数级但正确)

gen.cpp(数据生成器)—— 对拍质量的关键

gen 的唯一要求是:生成的数据必须符合题目输入格式。格式不合法,my 和 std 都可能 RE。

3.2 编写对拍总控程序(duipai.cpp)—— 逐行解析

下面是完整版 duipai.cpp,每行都有注释:

duipai.cpp(Windows 版,推荐新手使用)
#include <cstdlib>   // system() 需要
#include <cstdio>     // printf() 需要
#include <ctime>       // clock() 计时需要

int main() {
    int T = 0;              // 测试用例计数器

    while (true) {          // 无限循环,直到发现错误
        T++;

        // 【1】生成数据:gen.exe 的输出重定向到 data.in 文件
        system("gen.exe > data.in");

        // 【2】运行 std(暴力程序):从 data.in 读,输出到 std.out
        system("std.exe < data.in > std.out");

        // 【3】运行 my(待测程序):同样的输入,输出到 my.out
        double beg = clock();                    // 开始计时
        system("my.exe < data.in > my.out");
        double end = clock();                    // 结束计时

        // 【4】比较输出
        // fc 命令:比较两个文件;返回 0 表示相同,非 0 表示不同
        // > nul:隐藏 fc 的详细输出,只保留返回值
        if (system("fc my.out std.out > nul") != 0) {
            printf("========== WA at test #%d ==========\n", T);
            printf("Input saved in: data.in\n");
            printf("Your output:   my.out\n");
            printf("Correct output: std.out\n");
            return 0;           // 停止对拍
        } else {
            // 显示通过,顺带显示 my 的运行时间
            printf("Case #%d AC  (%.0lf ms)\n",
                   T, (end - beg) * 1000.0 / CLOCKS_PER_SEC);
        }
    }
    return 0;
}
💡 关键语法说明

⏱️ 关于测速的说明:上面代码用 clock() 测量 my.exe 的运行时间,但这个时间包含了操作系统启动进程和磁盘 I/O 的开销,不是纯算法运行时间,仅供参考。实际评测时间通常会更短。

3.3 编译与运行——每一步都不能少

⚠️ 常见错误:忘记编译直接运行 duipai.exe
duipai.cpp 里的 system("my.exe ...") 调用的是 my.exe 这个文件。 如果你没有用 g++ 把 my.cpp 编译成 my.exe,system() 会执行失败(找不到文件)。

完整编译命令(Windows,在 Dev-C++ 或 VS Code 终端里运行):

💡 使用 IDE 的同学看这里
如果你不习惯用命令行,完全可以不用敲下面的命令!只需在 IDE 里依次打开四个 .cpp 文件,分别点击一次"编译"(Dev-C++ 按 F9),生成好 .exe 后,最后单独运行 duipai.cpp 即可。
g++ my.cpp -o my.exe -O2 -std=c++14
g++ std.cpp -o std.exe -O2 -std=c++14
g++ gen.cpp -o gen.exe -O2 -std=c++14
g++ duipai.cpp -o duipai.exe -O2 -std=c++14

每条命令执行后,如果没看到报错,说明编译成功。编译完成后,直接运行:

duipai.exe

正常运行效果:

Case #1 AC  (0 ms)
Case #2 AC  (0 ms)
Case #3 AC  (0 ms)
...
========== WA at test #27 ==========
Input saved in: data.in
Your output:   my.out
Correct output: std.out

此时可以打开 data.in 查看触发 bug 的输入数据。

3.4 发现错误后:数据缩小实操

对拍停在了第 27 组(举例),data.in 里是一组 n=18 的随机数据,肉眼很难直接看出 bug。这时不要试图直接调试大数据,而要先把数据缩小

🔍 数据缩小(Minimization)三步走
  1. 改小 gen.cpp 的数据范围:把 n 的最大值从 20 改成 5(或更小),重新编译运行对拍
  2. 重复直到 n≤5:此时数据规模已小到可以手算,打开 data.in 直接验算
  3. 定位 bug 后修复:把 gen.cpp 的数据范围改回正常值,继续对拍验证修复是否成功

具体操作:

① 修改 gen.cpp,把 rand() % 20 + 1 改成 rand() % 5 + 1,重新编译 gen.exe

g++ gen.cpp -o gen.exe -O2 -std=c++14
duipai.exe

② 此时对拍会在很小的 n 下触发错误,data.in 里的数据可以直接手算:

3          <- n=3,手算完全可行
-5 -2 -10  <- 最大值应该是 -2,但 my 会输出 0

③ 找到 bug:ans 初始化为 0 在全负数时不成立。修复:

int ans = -2e9;   // 或者:int ans = a[0];  (先读第一个元素)

④ 修复后,把 gen.cpp 的 n 改回正常范围和边界值,再跑 10000 组确认无误。


第4章 随机数进阶:rand() vs mt19937

第3章的 gen.cpp 用了 srand(time(0))rand(),这在简单场景下够用,但有两个严重问题:

问题详情
范围太小Windows 下 RAND_MAX = 32767,无法生成 >32767 的随机数
种子重复srand(time(0)) 以秒为单位,1 秒内多次运行生成相同数据
质量差线性同余生成器,随机性不够好

4.1 mt19937:推荐方案

C++11 引入的 std::mt19937(梅森旋转算法)质量远优于 rand():

gen.cpp — mt19937 推荐模板
#include <bits/stdc++.h>
using namespace std;

// 用硬件随机源初始化(Linux 下效果更好)
mt19937 rng(random_device{}());

// 生成 [a, b] 范围内的随机整数
int rand_int(int a, int b) {
    return uniform_int_distribution<int>(a, b)(rng);
}

int main() {
    int n = rand_int(1, 100000);
    cout << n << endl;

    for (int i = 0; i < n; i++) {
        cout << rand_int(-1000000000, 1000000000);
        if (i < n - 1) cout << " ";
    }
    cout << endl;
    return 0;
}
✅ mt19937 优势总结

4.2 常见误区

误区正确做法
rng() % n(直接取模)uniform_int_distribution,避免模偏差
在循环内定义 mt19937 rng(...)rng 应定义为全局变量或只在 main 开头初始化一次
time(0) 做种子(秒级)random_device{}()chrono::steady_clock

第5章 常见数据类型的生成方法

好的数据生成器是对拍成功的一半。下面给出常见题型的数据生成模板。

5.1 数组 / 序列

gen.cpp — 生成随机数组
int n = rand_int(1, 100000);
cout << n << endl;
for (int i = 0; i < n; i++) {
    cout << rand_int(1, 1000000000);
    if (i < n - 1) cout << " ";
}
cout << endl;

5.2 排列(1~n 的随机排列)

gen.cpp — 生成随机排列
int n = rand_int(1, 100000);
cout << n << endl;

vector<int> p(n);
iota(p.begin(), p.end(), 1);
shuffle(p.begin(), p.end(), rng);
for (int i = 0; i < n; i++) {
    cout << p[i] << " \n"[i == n - 1];
}

5.3 树(无向连通)

gen.cpp — 生成随机树(n 点)
int n = rand_int(2, 100000);
cout << n << endl;
// 点 i 的父亲从 [1, i-1] 随机选,保证连通且无环
for (int i = 2; i <= n; i++) {
    cout << rand_int(1, i - 1) << " " << i << "\n";
}

5.4 图(无向连通图)

gen.cpp — 生成随机连通图
int n = rand_int(2, 1000);
// m 在 [n-1, n*(n-1)/2] 之间,保证连通
int m = rand_int(n - 1, min(n * (n - 1) / 2, 2000));
cout << n << " " << m << "\n";

// 先生成树(保证连通)
for (int i = 2; i <= n; i++) {
    cout << rand_int(1, i - 1) << " " << i << "\n";
}
// 补上剩余边
for (int i = n; i <= m; i++) {
    cout << rand_int(1, n) << " " << rand_int(1, n) << "\n";
}
⚠️ 图生成注意事项

5.5 字符串

gen.cpp — 生成随机字符串
int len = rand_int(1, 100000);
string s;
for (int i = 0; i < len; i++) {
    s += char('a' + rand_int(0, 25));
}
cout << s << endl;

第6章 对拍实战策略(分层测试)

对拍不是跑一次就完了。高效的测试需要分层进行,从易到难,全面覆盖。

第一层:小数据对拍

数据规模控制在暴力可承受范围内(如 n≤20),大量循环测试。快速发现逻辑 bug

✅ 必做

第二层:手工构造边界数据

针对特殊边界:全 0、全同、升序、降序、n=1、极端大值、图论中的菊花图/链等。

✅ 必做

第三层:大数据压测

用正解本身跑大数据(无暴力对比),检查 TLE/MLE/RE。观察运行时间是否在预期内。

⚠️ 对拍的补充

第四层:多生成器覆盖

编写多个 gen.cpp,分别侧重不同边界(如一个专出随机数据,一个专出有序数据)。

💡 进阶

✅ 推荐的考场流程
  1. 先写暴力(std.cpp),验证正确性
  2. 再写正解(my.cpp)
  3. 用小数据对拍,修复所有 WA
  4. 用手工边界数据额外验证
  5. 用大数据压测,确保不 TLE/RE
  6. 提交!

第7章 对拍的局限性(重要!)

⚠️ 对拍不能替代所有测试

很多新手在对拍 AC 后产生"万事大吉"的错觉。以下是对拍无法有效覆盖的场景:

局限说明应对方法
MLE(内存超限)对拍只比较输出,不检测内存使用用大数据手动压测,观察任务管理器/代码内存
极端大数据 bug暴力程序跑不了大数据,无法对比手工构造最大数据验证正解
随机数据未触发的边界 bug纯随机可能漏掉特定边界手工构造边界数据 + 多个生成器
时间复杂度分析对拍 AC 不代表算法复杂度正确人工分析复杂度,确保正解能在时限内通过
浮点精度问题微小误差可能导致 WA 但随机数据未触发SPJ 中用 eps 容差;手工测边界浮点值
💡 正确的心态
对拍是"最后一道保险",不是"第一道防火墙"。先确保算法思路正确、复杂度合理,再用对拍验证实现细节。

第8章 常见问题与解决方案

Q1:对拍一直 AC 但提交后 WA?

原因:数据规模不够大未触发 bug;std 和 my 有相同 bug;gen 未覆盖特定边界。

解决:增大测试量;用多个不同思路的 std 交叉验证;手工构造边界数据。

Q2:暴力程序太慢,跑不动?

解决:减小 gen 的数据规模;使用更优的暴力(如 O(n²) 而非 O(n³));限制对拍轮数。

Q3:浮点数精度问题导致误判?

解决:编写 SPJ(见附录D),用 fabs(a-b) < eps 判断;或输出时控制精度(如 printf("%.6f"))。

Q4:对拍时程序 RE / 死循环怎么办?

解决:在 gen 中控制数据范围,避免触发 RE;在 duipai.cpp 中加入超时检测。

Q5:需要 freopen 吗?

推荐:不用。用 shell 重定向(< data.in > my.out),代码中无需 freopen,避免提交时忘记删除导致 CE。

Q6:数据生成器每次数据都一样?

原因:srand(time(0)) 以秒为单位,编译运行太快导致种子相同。

解决:改用 mt19937 + random_device(见第4章)。


第9章 速查模板

文件结构

duipai/
├── my.cpp         ← 待测程序
├── std.cpp        ← 暴力程序(保证正确)
├── gen.cpp        ← 数据生成器
└── duipai.cpp    ← 对拍总控程序(本文件)

一键编译运行(Windows / Dev-C++ 终端)

g++ my.cpp -o my.exe -O2 -std=c++14
g++ std.cpp -o std.exe -O2 -std=c++14
g++ gen.cpp -o gen.exe -O2 -std=c++14
g++ duipai.cpp -o duipai.exe -O2 -std=c++14
duipai.exe

gen.cpp 标准模板(mt19937 版)

gen.cpp
#include <bits/stdc++.h>
using namespace std;
mt19937 rng(random_device{}());
int rand_int(int a, int b) {
    return uniform_int_distribution<int>(a, b)(rng);
}
int main() {
    // 根据题目格式生成数据,n 范围控制在暴力可接受范围内
    int n = rand_int(1, 20);
    cout << n << endl;
    for (int i = 0; i < n; i++) {
        cout << rand_int(1, 1000000000) << " \n"[i == n - 1];
    }
    return 0;
}

── 主线内容结束,以下为进阶附录 ──


附录A Windows 批处理对拍(替代方案)

如果你更习惯用批处理脚本而非 C++ 程序来对拍,可以用 .bat 文件。核心命令是 fc(文件比较)。

duipai.bat — 最简版
@echo off
:loop
gen.exe > data.in
std.exe < data.in > std.out
my.exe < data.in > my.out
fc std.out my.out > nul
if errorlevel 1 pause
goto loop
duipai.bat — 增强版(带计数 + 编译)
@echo off
echo Compiling...
g++ gen.cpp -o gen.exe -O2 -std=c++14
g++ std.cpp -o std.exe -O2 -std=c++14
g++ my.cpp -o my.exe -O2 -std=c++14
if errorlevel 1 (echo Compile failed & pause & exit)

set /a cnt=0
:loop
set /a cnt+=1
echo Case %cnt%...
gen.exe > data.in
std.exe < data.in > std.out
my.exe < data.in > my.out
fc std.out my.out > nul
if errorlevel 1 (
    echo ===== WA at Case %cnt% =====
    pause & exit
) else (
    echo Case %cnt% AC
)
goto loop
要点说明
fc 返回值0 = 文件相同,非 0 = 不同(与 diff 相反!)
if errorlevel 1返回值 ≥1 时触发(即文件不同)
> nul隐藏 fc 的详细输出,只保留返回值

附录B Linux / macOS Shell 对拍(替代方案)

Linux 或 macOS 下使用 .sh 脚本,核心命令是 diff

duipai.sh
#!/bin/bash
# 编译(每次运行前重新编译,确保代码最新)
g++ gen.cpp -o gen -O2 -std=c++14
g++ std.cpp -o std -O2 -std=c++14
g++ my.cpp -o my -O2 -std=c++14

cnt=0
while true; do
    cnt=$((cnt + 1))
    echo "Case $cnt..."

    ./gen > data.in
    ./std < data.in > std.out
    ./my < data.in > my.out

    if ! diff -q std.out my.out > /dev/null; then
        echo "===== WA at Case $cnt ====="
        echo "Input: data.in"
        exit 0
    else
        echo "Case $cnt AC"
    fi
done

使用方法:

chmod +x duipai.sh   # 首次运行前添加执行权限
./duipai.sh           # 运行
要点说明
diff 返回值0 = 文件相同,非 0 = 不同(与 fc 相反!)
diff -q只报告是否相同,不输出差异内容
/dev/null丢弃输出,只保留返回值

附录C Python 对拍(替代方案)

Python 版本跨平台性最好,无需区分 Windows/Linux 的命令差异。

duipai.py
import os, sys

cnt = 0
while True:
    cnt += 1
    print(f"Case {cnt}...", end=" ")

    # 生成数据
    os.system("gen.exe > data.in")
    # 运行两个程序
    os.system("std.exe < data.in > std.out")
    os.system("my.exe < data.in > my.out")

    # 比较输出(忽略空白字符差异)
    with open("std.out") as f: s1 = f.read().split()
    with open("my.out") as f: s2 = f.read().split()

    if s1 != s2:
        print(f"\n===== WA at Case {cnt} =====")
        print("Input: data.in")
        sys.exit(0)
    else:
        print("AC")

上面的 Python 版本用 split() 读取文件,自动忽略多余空格和换行,比直接用 fc/diff 更宽容。

附录D Special Judge(SPJ)与对拍(进阶)

当题目答案不唯一时(如输出任意一组合法方案、输出精度范围内的值),直接比对输出文件无法判定正确性,需要编写 SPJ(Special Judge)。

D.1 SPJ 的基本结构

SPJ 是一个独立程序,它读取「输入文件 + 选手输出文件」,判断选手输出是否合法且正确。

spj.cpp — 示例:判断最大子段和
#include <bits/stdc++.h>
using namespace std;

int main(int argc, char* argv[]) {
    // argv[1] = 输入文件, argv[2] = 标准输出, argv[3] = 选手输出
    ifstream fin(argv[1]), fstd(argv[2]), fmy(argv[3]);

    int n; fin >> n;
    vector<int> a(n);
    for (int& x : a) fin >> x;

    // 读取选手输出,验证合法性
    int l, r;
    fmy >> l >> r;
    if (l < 1 || r > n || l > r) return 1;  // 不合法

    long long sum = 0;
    for (int i = l - 1; i < r; i++) sum += a[i];

    // 与标准答案比较
    long long std_ans;
    fstd >> std_ans;
    if (sum != std_ans) return 1;
    return 0;  // 正确
}

D.2 在对拍中使用 SPJ

把 duipai.cpp 中的 system("fc ...") 替换为调用 SPJ:

// duipai.cpp 中替换比较部分
system("spj.exe data.in std.out my.out");
// 检查 SPJ 的返回值:0 = AC,非 0 = WA
if (system("spj.exe data.in std.out my.out") != 0) {
    printf("WA at test #%d\n", T);
    return 0;
}

D.3 使用 Testlib 编写 SPJ(推荐进阶用户)

Testlib 是行业标准,被 Codeforces、洛谷等平台采用:

spj.cpp — Testlib 版
#include "testlib.h"
int main(int argc, char* argv[]) {
    registerTestlibCmd(argc, argv);
    double pans = ouf.readDouble();
    double jans = ans.readDouble();
    if (fabs(pans - jans) < 1e-6)
        quitf(_ok, "Accepted\n");
    else
        quitf(_wa, "Expected %.6f, found %.6f\n", jans, pans);
}

附录E 交互题的对拍方法(进阶)

交互题的对拍比传统题复杂,因为程序之间存在"对话"。主要分为两种实现方式。

E.1 Grader 交互(NOI/IOI 风格)

对于下发 grader.cpp 的题,本地调试方法:

g++ my_solution.cpp grader.cpp -o test -O2 -std=c++14
./test

自对拍技巧:复制一份 grader.cpp,把交互函数实现放在文件末尾,选手代码放在文件开头,这样可以直接编译调试。提交时注意改回原始格式。

E.2 STDIO 交互(Codeforces 风格)

需要用管道(pipe)连接选手程序和交互器:

mkfifo pipe                    # 创建命名管道
./interactor < pipe | ./my_solution > pipe   # 连接

更简单的方式是将交互器逻辑内置到 C++ 程序中,用文件 I/O 模拟交互过程。

附录F 工具推荐(进阶)

Testlib

C++ 跨平台

最权威的出题辅助库(仅 testlib.h 一个头文件)。用于编写 Generator、Validator、Interactor、Checker。与 Codeforces Polygon 完全兼容。

GitHub: MikeMirzayanov/testlib

CYaRon

Python 跨平台

洛谷开发的 Python 库,内置随机图、树、字符串等数据结构生成器,可自动调用标程生成 .out 文件。

安装: pip install cyaron

CarYon

C++ 跨平台

基于 C++ 的出题工具,提供 caryon.h 头文件,内置 cyrand()、inint() 等函数简化数据生成。

Polygon

Web 跨平台

Codeforces 开发的在线出题平台,集成 Testlib 全部功能,支持可视化编辑测试数据。

网址: polygon.codeforces.com