HỆ THỐNG KIỂM THỬ MÃ NGUỒN CAO CẤP

Hướng Dẫn Thiết Lập Special Judge (SPJ)

Khi ra đề cho các bài toán thực tế, đôi khi việc so sánh khớp chuỗi thông thường không thể đáp ứng được (ví dụ: bài toán có nhiều đáp án hợp lệ, hoặc kết quả là số thực cho phép sai số trong khoảng nhỏ). Lúc này, bạn cần sử dụng Special Judge (SPJ) - một chương trình kiểm thử tùy biến được lập trình bằng C/C++ để làm trọng tài chấm điểm.

Standard Judge

Trình chấm tiêu chuẩn

So sánh trùng khớp 100% từng kí tự giữa kết quả của thí sinh (stdout) và file đáp án chuẩn (.out), bỏ qua khoảng trắng/xuống dòng thừa tùy cấu hình. Phù hợp cho bài toán chỉ có duy nhất một đáp án.

Special Judge (SPJ)

Trình chấm tùy biến

Sử dụng một chương trình C++ được biên dịch riêng chạy song song. Trình chấm này tiếp nhận file input gốc, file kết quả của thí sinh và file đáp án mẫu để chạy thuật toán kiểm tra logic nghiệp vụ phức tạp. Phù hợp cho đa đáp án và xử lý sai số.

Thông Số Biên Dịch & Chạy Trình Trọng Tài

  • Tham số biên dịch SPJ trên máy chủ: g++ -fno-asm -std=c++17 -O2.
  • Trình chấm của bạn sẽ được gọi trực tiếp bằng lệnh chạy dòng lệnh và truyền vào 3 tham số vị trí: ./spj in.txt out.txt ans.txt.
  • Trạng thái phản hồi: Trình SPJ trả về mã thoát thành công (return 0) tương ứng với kết quả chính xác (Accepted - AC), các giá trị khác 0 đại diện cho Wrong Answer (WA).

Nguyên Tắc An Toàn & Tránh Lỗi Biên Dịch

Tuyệt đối không gọi các lệnh hệ thống không liên quan, không cố tình can thiệp hệ thống tệp tin lõi của máy chủ. Cần đảm bảo mã nguồn SPJ chạy chuẩn xác tại môi trường cục bộ trước khi cập nhật. Nếu SPJ phát sinh lỗi Runtime hoặc tràn bộ nhớ khi chạy chấm, hệ thống sẽ tự động chấm điểm thí sinh là lỗi hệ thống.

Hai Phương Pháp Viết SPJ Phổ Biến

Lựa chọn cấu trúc phù hợp nhất với yêu cầu bài toán của bạn.

1

Cách 1: Sử Dụng Nhập/Xuất Tệp Tin Thuần C/C++

Phương pháp này nhận trực tiếp 3 tệp tin đầu vào từ đối số dòng lệnh (args[1] là tệp dữ liệu testcase gốc, args[2] là bài nộp thí sinh, args[3] là đáp án tham khảo). Thích hợp cho các bài kiểm tra logic đơn giản.

cpp_native_spj.cpp

Pure C/C++ I/O
#include <iostream>
#include <cmath>
#include <cstdio>

#define AC 0
#define WA 1

const double eps = 1e-4; // Sai số tối đa cho phép

int main(int argc, char *args[]) {
    // args[1]: File input đầu vào (.in)
    // args[2]: File output của học sinh (.out nộp từ IDE)
    // args[3]: File answer chuẩn của giáo viên (.ans/.out chuẩn)
    FILE *f_in   = fopen(args[1], "r");
    FILE *f_user = fopen(args[2], "r");
    FILE *f_out  = fopen(args[3], "r");

    int ret = AC;
    int t;
    double a, x;

    // Đọc số bộ testcase từ input
    if (fscanf(f_in, "%d", &t) != EOF) {
        while (t--) {
            fscanf(f_out, "%lf", &a);  // Đáp án chuẩn
            fscanf(f_user, "%lf", &x); // Đáp án học sinh nộp

            // Kiểm tra điều kiện sai số thực
            if (std::isnan(x) || std::isinf(x) || std::fabs(a - x) > eps) {
                ret = WA;
                std::cerr << "Lỗi đáp án: Kỳ vọng = " << a << ", Thực tế học sinh = " << x << std::endl;
                break;
            }
        }
    }

    fclose(f_in);
    fclose(f_user);
    fclose(f_out);
    return ret;
}
2

Cách 2: Sử Dụng Thư Viện Nâng Cao testlib.h (Khuyến Nghị)

Hệ thống ITCoder tích hợp sẵn thư viện nổi tiếng testlib.h tương tự nền tảng Codeforces. Thư viện này hỗ trợ xử lý luồng cực mạnh, tự động kiểm tra định dạng dữ liệu đầu vào và cung cấp các hàm phản hồi log chi tiết hữu ích lên hệ thống.

Các luồng tệp tin được chuẩn hóa tự động trong lớp: inf (dữ liệu vào), ouf (bài làm của thí sinh), và ans (đáp án chuẩn).

testlib_spj.cpp

Codeforces Testlib System
#include "testlib.h"
#include <cmath>

int main(int argc, char* argv[]) {
    // Đăng ký tham số chạy dòng lệnh với thư viện testlib
    registerTestlibCmd(argc, argv);

    // Lặp duyệt hết các kết quả trong tệp tin đáp án chuẩn
    while (!ans.eof()) {
        double jans = ans.readDouble(); // Lấy giá trị đáp án của giám khảo (Judge Answer)
        double pans = ouf.readDouble(); // Lấy giá trị bài làm của học sinh (Participant Answer)
        ans.readEoln(); // Di chuyển con trỏ tới cuối dòng đáp án chuẩn

        // Kiểm tra sai số trong khoảng cho phép (ví dụ 0.01)
        if (std::isnan(pans) || std::isinf(pans) || std::fabs(pans - jans) > 0.01) {
            // quitf() tự động ghi log báo lỗi và kết thúc chương trình với mã WA
            quitf(_wa, "Ket qua sai: Mong doi = %.4f, Doc duoc = %.4f", jans, pans);
        }
    }

    // Trả về AC thành công nếu toàn bộ dữ liệu kiểm thử trùng khớp
    quitf(_ok, "Ket qua hoan toan chinh xac.");
    return 0;
}

Tham khảo tài liệu hướng dẫn và tải tệp thư viện gốc tại repository chính thức: MikeMirzayanov/testlib Github

Kiểm Thử SPJ Ở Môi Trường Local

Trước khi đẩy mã nguồn trọng tài lên máy chủ, bạn nên tự biên dịch và kiểm thử tại máy cá nhân thông qua terminal hoặc command prompt để tránh các lỗi logic:

Bước 1: Biên dịch chương trình trọng tài (C++)

g++ -O2 spj.cpp -o spj

Bước 2: Thực thi kiểm tra với các tệp dữ liệu testcase

./spj in.txt user_out.txt ans.txt

* Trong đó: in.txt chứa dữ liệu vào, user_out.txt chứa kết quả bài nộp của thí sinh, ans.txt chứa đáp án mẫu.

Bước 3: Đánh giá mã thoát trả về (Exit Code)

echo $?

* Mã trả về là 0 tức là chương trình SPJ đánh giá bài làm hợp lệ (AC). Bất kỳ mã trả về nào khác đại diện cho sự thất bại (WA).

Nếu bạn gặp bất kỳ trở ngại nào trong việc biên dịch và chạy trình trọng tài, hãy liên hệ với Ban quản trị hệ thống ITCoder để nhận hỗ trợ giải quyết mã nguồn và nạp testcase tối ưu nhất.