295 lines
6.8 KiB
Markdown
295 lines
6.8 KiB
Markdown
# BitOJ 自定义验证程序完整指南
|
||
|
||
## 概述
|
||
|
||
BitOJ系统支持4种判题方式:
|
||
|
||
1. **字符串精确比较** - 逐字符对比输出
|
||
2. **字符串容错比较** - 允许格式错误(PE),主要比较数字内容
|
||
3. **文件MD5校验** - 比较文件大小和MD5值
|
||
4. **自定义验证程序** - 编写专门的验证逻辑
|
||
|
||
本文档重点介绍第4种方式:自定义验证程序的编写和使用。
|
||
|
||
## 自定义验证程序原理
|
||
|
||
### 工作流程
|
||
1. 系统执行用户提交的代码
|
||
2. 将输入、期望输出、实际输出传递给验证程序
|
||
3. 验证程序分析这些文件并返回判题结果
|
||
4. 系统根据返回结果确定最终评分
|
||
|
||
### 参数说明
|
||
验证程序接收以下参数:
|
||
- `argv[1]`: 标准输入文件路径
|
||
- `argv[2]`: 期望输出文件路径
|
||
- `argv[3]`: 用户程序输出文件路径
|
||
|
||
### 返回值
|
||
验证程序必须输出以下结果之一:
|
||
- `AC` - Accepted(答案正确)
|
||
- `PE` - Presentation Error(格式错误)
|
||
- `WA` - Wrong Answer(答案错误)
|
||
|
||
## 实际示例
|
||
|
||
### 1. 浮点数误差比较 (C++)
|
||
|
||
**适用场景**: 计算几何、数值计算等需要浮点数比较的题目
|
||
|
||
```cpp
|
||
#include <iostream>
|
||
#include <fstream>
|
||
#include <cmath>
|
||
using namespace std;
|
||
|
||
const double EPS = 1e-9;
|
||
|
||
int main(int argc, char* argv[]) {
|
||
ifstream expected(argv[2]); // 标准答案
|
||
ifstream result(argv[3]); // 用户输出
|
||
|
||
double exp_val, res_val;
|
||
if (!(expected >> exp_val) || !(result >> res_val)) {
|
||
cout << "WA" << endl;
|
||
return 0;
|
||
}
|
||
|
||
if (fabs(exp_val - res_val) <= EPS) {
|
||
cout << "AC" << endl;
|
||
} else {
|
||
cout << "WA" << endl;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
### 2. 多解答案验证 (Python)
|
||
|
||
**适用场景**: 排序题、图论路径题等有多种正确答案的题目
|
||
|
||
```python
|
||
#!/usr/bin/env python
|
||
import sys
|
||
|
||
def main():
|
||
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 = sorted(map(int, expected.split()))
|
||
result_nums = sorted(map(int, result.split()))
|
||
|
||
if expected_nums == result_nums:
|
||
print("AC")
|
||
else:
|
||
print("WA")
|
||
except:
|
||
print("WA")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|
||
```
|
||
|
||
### 3. 输出格式验证 (Java)
|
||
|
||
**适用场景**: 需要严格输出格式的竞赛题目
|
||
|
||
```java
|
||
import java.io.*;
|
||
import java.util.*;
|
||
import java.util.regex.Pattern;
|
||
|
||
public class StringFormatValidator {
|
||
public static void main(String[] args) {
|
||
try {
|
||
Scanner result = new Scanner(new File(args[2]));
|
||
|
||
int lineNum = 1;
|
||
while (result.hasNextLine()) {
|
||
String line = result.nextLine().trim();
|
||
|
||
// 检查格式:Case #X: Y
|
||
Pattern pattern = Pattern.compile("^Case #" + lineNum + ": \\d+$");
|
||
if (!pattern.matcher(line).matches()) {
|
||
System.out.println("PE");
|
||
return;
|
||
}
|
||
lineNum++;
|
||
}
|
||
|
||
System.out.println("AC");
|
||
} catch (Exception e) {
|
||
System.out.println("WA");
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 在BitOJ中配置自定义验证程序
|
||
|
||
### 代码配置示例
|
||
|
||
```python
|
||
from judgescript import ExternalJudge
|
||
|
||
# 创建自定义验证程序
|
||
validator = ExternalJudge(
|
||
problemid='P1001', # 题目ID
|
||
vlang='gcc-3.3', # 验证程序语言
|
||
vcode=validator_code # 验证程序源代码
|
||
)
|
||
|
||
# 在测试过程中调用
|
||
result = validator.judge(
|
||
sid='S123', # 提交ID
|
||
tid='T001', # 测试用例ID
|
||
tin='/path/to/input.txt', # 输入文件
|
||
tout='/path/to/output.txt', # 期望输出
|
||
result='/path/to/result.txt', # 实际输出
|
||
errfile='/path/to/error.txt' # 错误输出
|
||
)
|
||
```
|
||
|
||
### 支持的编程语言
|
||
|
||
| 语言 | 标识符 | 说明 |
|
||
|------|--------|------|
|
||
| C | gcc-3.3 | 适合高性能验证 |
|
||
| C++ | g++-3.3 | 支持STL容器 |
|
||
| Java | java-1.6 | 强类型安全 |
|
||
| Python | python-2.5 | 快速开发 |
|
||
| Pascal | fpc-2.2 | 简单逻辑 |
|
||
|
||
## 最佳实践
|
||
|
||
### 1. 错误处理
|
||
```cpp
|
||
// 文件读取失败处理
|
||
if (!input_file.is_open()) {
|
||
cout << "WA" << endl;
|
||
return 0;
|
||
}
|
||
|
||
// 数据解析失败处理
|
||
try {
|
||
value = stod(line);
|
||
} catch (const exception& e) {
|
||
cout << "WA" << endl;
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
### 2. 性能优化
|
||
- 使用适当的数据结构
|
||
- 避免不必要的字符串操作
|
||
- 合理设置缓冲区大小
|
||
- 注意内存使用限制
|
||
|
||
### 3. 安全考虑
|
||
- 验证输入参数数量
|
||
- 处理异常文件路径
|
||
- 限制递归深度
|
||
- 避免无限循环
|
||
|
||
### 4. 可维护性
|
||
- 添加清晰的注释
|
||
- 使用有意义的变量名
|
||
- 模块化验证逻辑
|
||
- 提供调试输出选项
|
||
|
||
## 常见应用场景
|
||
|
||
### 1. 数值计算题
|
||
- 浮点数精度比较
|
||
- 矩阵运算结果验证
|
||
- 统计数值的近似比较
|
||
|
||
### 2. 图论算法题
|
||
- 最短路径验证(允许多条等长路径)
|
||
- 最小生成树验证(权重相等即可)
|
||
- 图遍历结果验证
|
||
|
||
### 3. 字符串处理题
|
||
- 忽略大小写比较
|
||
- 忽略空白字符差异
|
||
- 正则表达式匹配
|
||
|
||
### 4. 组合数学题
|
||
- 排列组合结果验证
|
||
- 集合操作结果比较
|
||
- 数学证明步骤检查
|
||
|
||
### 5. 交互式题目
|
||
- 模拟交互过程
|
||
- 验证查询次数限制
|
||
- 检查答案正确性
|
||
|
||
## 调试技巧
|
||
|
||
### 1. 本地测试
|
||
```bash
|
||
# 编译验证程序
|
||
g++ -o validator validator.cpp
|
||
|
||
# 准备测试文件
|
||
echo "3.14159" > expected.txt
|
||
echo "3.14160" > result.txt
|
||
|
||
# 运行测试
|
||
./validator input.txt expected.txt result.txt
|
||
```
|
||
|
||
### 2. 日志输出
|
||
```cpp
|
||
#ifdef DEBUG
|
||
cerr << "Expected: " << exp_val << ", Got: " << res_val << endl;
|
||
cerr << "Difference: " << fabs(exp_val - res_val) << endl;
|
||
#endif
|
||
```
|
||
|
||
### 3. 边界情况测试
|
||
- 空输出文件
|
||
- 超大数值
|
||
- 特殊字符
|
||
- 格式错误的输入
|
||
|
||
## 故障排除
|
||
|
||
### 常见问题
|
||
|
||
1. **编译失败**
|
||
- 检查语言版本兼容性
|
||
- 确认头文件引用正确
|
||
- 验证语法错误
|
||
|
||
2. **运行超时**
|
||
- 优化算法复杂度
|
||
- 减少不必要的计算
|
||
- 检查是否有死循环
|
||
|
||
3. **内存不足**
|
||
- 减少内存分配
|
||
- 及时释放资源
|
||
- 使用流式处理
|
||
|
||
4. **判题结果异常**
|
||
- 验证输出格式
|
||
- 检查返回值规范
|
||
- 确认异常处理逻辑
|
||
|
||
## 总结
|
||
|
||
自定义验证程序是BitOJ系统最灵活的判题方式,能够处理各种复杂的判题需求。通过合理设计验证逻辑,可以支持:
|
||
|
||
- 多解答案题目
|
||
- 浮点数精度要求
|
||
- 特殊输出格式
|
||
- 交互式评测
|
||
- 复杂的正确性检查
|
||
|
||
掌握自定义验证程序的编写技巧,能够大大扩展在线评测系统的应用范围,为各种类型的编程竞赛和教学需求提供支持。 |