Files
bitoj_python/examples/custom_validators.md

8.0 KiB
Raw Permalink Blame History

BitOJ 自定义验证程序示例

1. 基本验证程序结构

自定义验证程序接收以下参数:

  • 标准输入文件
  • 标准输出文件(期望答案)
  • 用户输出文件(用户程序的输出)

验证程序需要输出以下结果之一:

  • AC - 答案正确
  • PE - 格式错误(如果支持)
  • WA - 答案错误

2. 示例1数值误差比较验证程序C++

适用于需要浮点数比较的题目:

#include <iostream>
#include <fstream>
#include <cmath>
#include <string>
using namespace std;

const double EPS = 1e-9;

int main(int argc, char* argv[]) {
    // argv[1]: 标准输入文件
    // argv[2]: 标准输出文件
    // argv[3]: 用户输出文件
    
    ifstream expected(argv[2]);  // 标准答案
    ifstream result(argv[3]);    // 用户输出
    
    double exp_val, res_val;
    
    // 读取期望的浮点数
    if (!(expected >> exp_val)) {
        cout << "WA" << endl;
        return 0;
    }
    
    // 读取用户输出的浮点数
    if (!(result >> res_val)) {
        cout << "WA" << endl;
        return 0;
    }
    
    // 比较数值,允许误差
    if (fabs(exp_val - res_val) <= EPS) {
        cout << "AC" << endl;
    } else {
        cout << "WA" << endl;
    }
    
    return 0;
}

3. 示例2多解答案验证程序Python

适用于有多个正确答案的题目:

#!/usr/bin/env python
import sys

def main():
    # sys.argv[1]: 标准输入文件
    # sys.argv[2]: 标准输出文件
    # sys.argv[3]: 用户输出文件
    
    try:
        with open(sys.argv[2], 'r') as f:
            expected = f.read().strip()
        
        with open(sys.argv[3], 'r') as f:
            result = f.read().strip()
        
        # 假设这是一个排序题目,任何正确的排序都是对的
        expected_nums = list(map(int, expected.split()))
        result_nums = list(map(int, result.split()))
        
        # 检查数量是否相同
        if len(expected_nums) != len(result_nums):
            print("WA")
            return
        
        # 检查排序后是否相同(原始数据相同)
        if sorted(expected_nums) != sorted(result_nums):
            print("WA")
            return
        
        # 检查用户输出是否有序
        if result_nums == sorted(result_nums):
            print("AC")
        else:
            print("WA")
            
    except Exception as e:
        print("WA")

if __name__ == "__main__":
    main()

4. 示例3图论路径验证程序C++

验证最短路径问题,检查路径是否合法且最优:

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
using namespace std;

struct Edge {
    int to, weight;
};

int main(int argc, char* argv[]) {
    ifstream input(argv[1]);     // 输入数据
    ifstream expected(argv[2]);  // 期望答案
    ifstream result(argv[3]);    // 用户答案
    
    // 读取图的信息
    int n, m;
    input >> n >> m;
    
    vector<vector<Edge>> graph(n + 1);
    for (int i = 0; i < m; i++) {
        int u, v, w;
        input >> u >> v >> w;
        graph[u].push_back({v, w});
        graph[v].push_back({u, w});  // 无向图
    }
    
    int start, end;
    input >> start >> end;
    
    // 读取期望的最短距离
    int expected_dist;
    expected >> expected_dist;
    
    // 读取用户输出的路径
    string line;
    getline(result, line);
    istringstream iss(line);
    vector<int> path;
    int node;
    while (iss >> node) {
        path.push_back(node);
    }
    
    // 验证路径
    if (path.empty() || path[0] != start || path.back() != end) {
        cout << "WA" << endl;
        return 0;
    }
    
    // 计算路径长度
    int total_dist = 0;
    for (int i = 0; i < path.size() - 1; i++) {
        int u = path[i], v = path[i + 1];
        bool found = false;
        for (const Edge& e : graph[u]) {
            if (e.to == v) {
                total_dist += e.weight;
                found = true;
                break;
            }
        }
        if (!found) {
            cout << "WA" << endl;  // 路径不存在
            return 0;
        }
    }
    
    // 检查是否是最短路径
    if (total_dist == expected_dist) {
        cout << "AC" << endl;
    } else {
        cout << "WA" << endl;
    }
    
    return 0;
}

5. 示例4字符串格式验证程序Java

验证输出格式是否正确:

import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        // args[0]: 标准输入文件
        // args[1]: 标准输出文件
        // args[2]: 用户输出文件
        
        Scanner expected = new Scanner(new File(args[1]));
        Scanner result = new Scanner(new File(args[2]));
        
        try {
            // 读取期望的行数
            int expectedLines = expected.nextInt();
            
            // 检查用户输出的行数
            int actualLines = 0;
            List<String> userLines = new ArrayList<>();
            
            while (result.hasNextLine()) {
                String line = result.nextLine().trim();
                if (!line.isEmpty()) {
                    userLines.add(line);
                    actualLines++;
                }
            }
            
            if (actualLines != expectedLines) {
                System.out.println("WA");
                return;
            }
            
            // 检查每行格式:必须是 "Case #X: Y" 的格式
            for (int i = 0; i < userLines.size(); i++) {
                String line = userLines.get(i);
                String pattern = "Case #" + (i + 1) + ": \\d+";
                if (!line.matches(pattern)) {
                    System.out.println("PE");  // 格式错误
                    return;
                }
            }
            
            System.out.println("AC");
            
        } catch (Exception e) {
            System.out.println("WA");
        } finally {
            expected.close();
            result.close();
        }
    }
}

6. 示例5交互式题目验证程序C++

模拟交互过程:

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
using namespace std;

int main(int argc, char* argv[]) {
    ifstream input(argv[1]);     // 输入数据
    ifstream expected(argv[2]);  // 期望的查询次数等
    ifstream result(argv[3]);    // 用户的查询序列
    
    // 读取隐藏的数字
    int secret;
    input >> secret;
    
    // 读取最大查询次数
    int maxQueries;
    expected >> maxQueries;
    
    // 模拟交互过程
    int queryCount = 0;
    int guess;
    bool found = false;
    
    while (result >> guess && queryCount < maxQueries) {
        queryCount++;
        
        if (guess == secret) {
            found = true;
            break;
        }
        // 在实际交互中,这里会给用户反馈 "too high" 或 "too low"
    }
    
    // 检查结果
    if (found && queryCount <= maxQueries) {
        cout << "AC" << endl;
    } else {
        cout << "WA" << endl;
    }
    
    return 0;
}

使用方法

  1. 编写验证程序:选择合适的编程语言编写验证逻辑
  2. 设置题目:在题目配置中指定使用自定义验证程序
  3. 上传验证代码:将验证程序代码保存到系统中
  4. 测试验证:确保验证程序能正确处理各种情况

注意事项

  1. 异常处理:验证程序必须处理所有可能的异常情况
  2. 性能考虑:验证程序也有时间限制,需要高效实现
  3. 输出格式:验证程序必须严格按照 AC/PE/WA 格式输出
  4. 安全性:验证程序在沙箱环境中运行,有资源限制
  5. 可移植性:确保验证程序在评测环境中能正常编译运行

配置示例

在 BitOJ 中配置自定义验证程序:

# 在题目配置中设置自定义验证程序
problem.validator_language = 'gcc-3.3'  # 验证程序语言
problem.validator_code = '''
// 这里放置验证程序的源代码
'''