Khi nào cần custom checker?
Mặc định, CTOJ so sánh output của thí sinh với output chuẩn từng ký tự (bỏ qua khoảng trắng thừa cuối dòng). Điều này hoạt động tốt cho hầu hết bài toán. Tuy nhiên, bạn cần custom checker khi:
- Nhiều đáp án đúng: Bài yêu cầu "in một hoán vị thỏa điều kiện" — có thể có nhiều hoán vị đúng khác nhau.
- Sai số số thực: Bài yêu cầu "in kết quả với sai số " — cần so sánh với tolerance.
- Output phụ thuộc vào input: Cần đọc input gốc để kiểm tra tính đúng đắn (ví dụ: kiểm tra output có phải là cây khung nhỏ nhất không).
- Định dạng linh hoạt: Chấp nhận nhiều cách trình bày khác nhau cho cùng một đáp án.
Loại checker trên CTOJ
CTOJ hỗ trợ checker kiểu bridged — sử dụng thư viện testlib.h. Đây là chuẩn phổ biến nhất trong competitive programming (Codeforces, Polygon, ...).
Khi tạo bài, thiết lập:
checker = "bridged"custom_checker_source= mã nguồn C++ của checker
Cấu trúc checker testlib
#include "testlib.h"
using namespace std;
int main(int argc, char* argv[]) {
registerTestlibCmd(argc, argv);
// Đọc dữ liệu từ 3 stream:
// inf — file input gốc (.in)
// ouf — output của thí sinh
// ans — output chuẩn (.out)
// Kiểm tra và trả verdict
quitf(_ok, "correct"); // AC
quitf(_wa, "wrong"); // WA
}
Ba stream quan trọng
| Stream | Ý nghĩa | Dùng khi |
|---|---|---|
inf |
File input gốc (.in) |
Đọc , mảng, đồ thị... để kiểm tra output có hợp lệ không |
ouf |
Output của thí sinh | Luôn dùng — đây là cái cần kiểm tra |
ans |
Output chuẩn (.out) |
Đọc đáp án mẫu nếu cần so sánh (ví dụ: so giá trị tối ưu) |
Các hàm đọc phổ biến
int n = inf.readInt(); // đọc một int
long long x = inf.readLong(); // đọc một long long
double d = ouf.readDouble(); // đọc một double
string s = ouf.readToken(); // đọc một token (không khoảng trắng)
string line = ouf.readLine(); // đọc cả dòng
int v = ouf.readInt(1, n, "vertex"); // đọc int trong [1, n], tên biến cho thông báo lỗi
Các verdict
quitf(_ok, "message"); // Accepted
quitf(_wa, "message"); // Wrong Answer
quitf(_pe, "message"); // Presentation Error (hiếm dùng)
quitf(_fail, "message"); // Judge Error — dùng khi phát hiện lỗi trong test/checker
Ví dụ 1: Bài có nhiều đáp án đúng
Bài toán: Cho chuỗi , sắp xếp lại các ký tự sao cho không có hai ký tự liền kề giống nhau. In IMPOSSIBLE nếu không thể.
#include "testlib.h"
#include <string>
#include <map>
#include <algorithm>
using namespace std;
int main(int argc, char* argv[]) {
registerTestlibCmd(argc, argv);
int n = inf.readInt();
string s = inf.readToken();
string out = ouf.readToken();
// Kiểm tra có khả thi không
map<char,int> cnt;
for (char c : s) cnt[c]++;
int maxf = 0;
for (auto& [k, v] : cnt) maxf = max(maxf, v);
bool possible = maxf <= (n + 1) / 2;
// Xử lý IMPOSSIBLE
string up = out;
for (auto& ch : up) ch = toupper(ch);
if (up == "IMPOSSIBLE") {
if (possible)
quitf(_wa, "printed IMPOSSIBLE but a valid arrangement exists");
quitf(_ok, "correctly reported infeasible");
}
if (!possible)
quitf(_wa, "no valid arrangement exists but contestant printed %s",
out.c_str());
// Kiểm tra output hợp lệ
if ((int)out.length() != n)
quitf(_wa, "output length %d != expected %d",
(int)out.length(), n);
map<char,int> ac;
for (char c : out) ac[c]++;
if (ac != cnt)
quitf(_wa, "output is not a permutation of input");
for (int i = 1; i < n; i++)
if (out[i] == out[i-1])
quitf(_wa, "adjacent duplicate at position %d", i);
quitf(_ok, "valid rearrangement");
}
Giải thích: Checker đọc chuỗi gốc từ inf, đọc output thí sinh từ ouf, rồi kiểm tra:
- Nếu thí sinh in IMPOSSIBLE → kiểm tra có thực sự bất khả thi không.
- Nếu thí sinh in kết quả → kiểm tra là hoán vị của chuỗi gốc và không có ký tự liền kề giống nhau.
Ví dụ 2: So sánh số thực với sai số
Bài toán: Tính khoảng cách ngắn nhất giữa hai điểm. Chấp nhận sai số .
#include "testlib.h"
#include <cmath>
using namespace std;
int main(int argc, char* argv[]) {
registerTestlibCmd(argc, argv);
double expected = ans.readDouble();
double got = ouf.readDouble();
// So sánh với sai số tuyệt đối và tương đối
if (abs(expected - got) <= 1e-6 ||
abs(expected - got) <= 1e-6 * abs(expected)) {
quitf(_ok, "answer %.10f, expected %.10f", got, expected);
}
quitf(_wa, "answer %.10f, expected %.10f, diff %.10f",
got, expected, abs(expected - got));
}
Ví dụ 3: Kiểm tra giá trị tối ưu + output hợp lệ
Bài toán: Tìm đường đi ngắn nhất trong đồ thị. In độ dài và đường đi.
#include "testlib.h"
#include <vector>
using namespace std;
int main(int argc, char* argv[]) {
registerTestlibCmd(argc, argv);
int n = inf.readInt();
int m = inf.readInt();
// Đọc đồ thị từ input
vector<vector<pair<int,int>>> adj(n + 1);
for (int i = 0; i < m; i++) {
int u = inf.readInt(), v = inf.readInt(), w = inf.readInt();
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
long long expected_dist = ans.readLong();
long long got_dist = ouf.readLong();
// Kiểm tra giá trị tối ưu
if (got_dist != expected_dist)
quitf(_wa, "distance %lld != expected %lld", got_dist, expected_dist);
// Kiểm tra đường đi hợp lệ
int k = ouf.readInt();
vector<int> path(k);
for (int i = 0; i < k; i++)
path[i] = ouf.readInt(1, n, "node");
if (path[0] != 1 || path[k-1] != n)
quitf(_wa, "path must start at 1 and end at %d", n);
long long total = 0;
for (int i = 1; i < k; i++) {
bool found = false;
for (auto [v, w] : adj[path[i-1]]) {
if (v == path[i]) {
total += w;
found = true;
break;
}
}
if (!found)
quitf(_wa, "no edge between %d and %d", path[i-1], path[i]);
}
if (total != got_dist)
quitf(_wa, "path cost %lld != claimed distance %lld", total, got_dist);
quitf(_ok, "valid shortest path of length %lld", got_dist);
}
Lưu ý khi viết checker
Dùng _fail cho lỗi judge
Nếu checker phát hiện dữ liệu test bị hỏng (không phải lỗi thí sinh), dùng _fail:
if ((int)s.length() != n)
quitf(_fail, "judge input malformed: string length %d != n=%d",
(int)s.length(), n);
Verdict _fail sẽ hiện thành Internal Error (IE) — cho biết lỗi thuộc về hệ thống, không phải thí sinh.
Đọc hết output thí sinh
Nếu thí sinh in thừa dữ liệu, checker nên phát hiện:
// Sau khi đọc xong output cần thiết
if (ouf.seekEof()) {
quitf(_ok, "correct");
} else {
quitf(_wa, "extra output after answer");
}
Tuy nhiên, trong thực tế nhiều checker bỏ qua bước này vì hệ thống thường không penalize output thừa.
Test checker cẩn thận
Trước khi deploy, hãy đảm bảo:
- Output đúng → AC
- Output sai (sai giá trị) → WA
- Output sai (sai format, thiếu dữ liệu) → WA
- IMPOSSIBLE khi có lời giải → WA
- Có lời giải khi IMPOSSIBLE → WA
Lưu checker trong problem package
Lưu file checker tại solutions/checker.cpp trong thư mục bài toán, cùng với lời giải mẫu. Điều này giúp theo dõi và cập nhật checker khi cần.
Tóm tắt
| Tình huống | Cần checker? | Loại |
|---|---|---|
| Output duy nhất | Không | Mặc định (so sánh text) |
| Nhiều đáp án đúng | Có | bridged + testlib |
| Sai số số thực | Có | bridged + testlib |
| Output phụ thuộc input | Có | bridged + testlib |
| Chỉ kiểm tra giá trị tối ưu | Có | bridged + testlib |
Bình luận