最近有几位同学来问我 Special Judge 怎么写?为了让大家可以写出 Special Judge 方便在本地调试和对拍,我就想写一篇文章来介绍 Special Judge。
Special Judge 是什么?有什么用?
大家可以先看这样一篇文章:Special Judge - OI Wiki (oi-wiki.org)。
Special Judge(简称:SPJ,别名:checker)是当一道题有多组解时,用来判断答案合法性的程序。
—— OI - Wiki
Special Judge 的写法
下面这两篇文章是关于 Special Judge 的写法:
因为洛谷使用的是 Codeforces 的 Testlib,所以就按照 Testlib 的 SPJ 写法即可。
但是本篇文章的重点不在于怎么编写 Special Judge,因为上面的文章都给出了很详细的写法,而是如何利用它来进行本地调试和对拍。
怎么利用 Special Judge 进行对拍
鉴于 CodeForces 自带的 Testlib 不好用,也有可能是因为我不会用,所以我手写了一个 Testlib 来方便调试。
只需要把下面的代码保存为 testlib.h
和 checker 在同一文件夹下即可。
//the code is from chenjh
#include<cstdio>
#include<string>
#include<cassert>
#include<regex>
#include<windows.h>
struct TestLib{
FILE *f;
char readChar(){char c;std::fscanf(f,"%c",&c);return c;}
char readChar(char ch){char c=readChar();assert(c==ch);return c;}
char readSpace(){char c=readChar();assert(c==' ');return c;}
void unreadChar(char ch){/*暂且不会实现。*/}
std::string readToken(){char s[1<<20];std::fscanf(f,"%s",s);std::string str=s;return str;}
std::string readToken(std::string regex){std::string s=readToken();assert(regex_match(s,std::regex(regex)));return s;}
std::string readWord(){return readToken();}
std::string readWord(std::string regex){return readToken(regex);}
long long readLong(){long long x;std::fscanf(f,"%lld",&x);return x;}
long long readLong(const long long&L,const long long&R){long long x=readLong();
assert(L<=x && x<=R);return x;}
int readInt(){int x;std::fscanf(f,"%d",&x);return x;}
int readInteger(){return readInt();}
int readInt(int L,int R){int x=readInt();assert(L<=x && x<=R);return x;}
double readDouble(){double x;std::fscanf(f,"%lf",&x);return x;}
double readReal(){return readDouble();}
double readDouble(const double&L,const double&R){double x=readDouble();
assert(L<=x && x<=R);return x;}
std::string readLine(){char s[1<<20];std::fscanf(f,"%[^\n]",s);std::string str=s;return str;}
std::string readString(){return readLine();}
void readEoln(){char c;std::fscanf(f,"%c",&c);assert(c=='\r'||c=='\n');if(c=='\r')std::fscanf(f,"%c",&c);assert(c=='\n');}
void readEof(){char c;std::fscanf(f,"%c",&c);assert(~c);}
}inf,ouf,ans;
void registerTestlibCmd(int argc,char* argv[]){
inf.f=std::fopen("data.in","r");
ouf.f=std::fopen("data.out","r");
ans.f=std::fopen("data.ans","r");
}
/*
Color:
需要 16 进制 0x 前缀。
0 = 黑色 8 = 灰色
1 = 蓝色 9 = 淡蓝色
2 = 绿色 A = 淡绿色
3 = 浅绿色 B = 淡浅绿色
4 = 红色 C = 淡红色
5 = 紫色 D = 淡紫色
6 = 黄色 E = 淡黄色
7 = 白色 F = 亮白色
*/
#ifndef CHECK
template<typename... Args>void COLOR_PRINT(const int color,const char*s,Args...x){
HANDLE handle=GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(handle,FOREGROUND_INTENSITY|color);
std::printf(s,x...);
SetConsoleTextAttribute(handle,FOREGROUND_INTENSITY|7);
}
#endif
const int _ok=0,_wa=1;
template<typename... Args>void quitf(const int ret,const char*s,Args...x){
#ifndef CHECK
std::printf("Score: ");
if(ret) COLOR_PRINT(0xa,"100\n");
else COLOR_PRINT(0xa,"0\n");
std::puts("Checker comment");
if(ret) COLOR_PRINT(0x4,"wa ");
else COLOR_PRINT(0xa,"ok ");
COLOR_PRINT(0x8,s,x...);
std::putchar('\n');
#endif
std::exit(ret);
}
template<typename... Args>void quitp(const double&ret,const char*s,Args...x){
#ifndef CHECK
std::printf("Score: ");
COLOR_PRINT(0xa,"%.1lf\n",ret);
std::puts("Checker comment");
COLOR_PRINT(0x4,"wa ");
COLOR_PRINT(0x8,s,x...);
#endif
std::putchar('\n'),std::exit(_wa);
}
因为 void unreadChar(char ch)
暂且还不会实现,我猜你们也不会用到这些函数,如果有大佬会实现,欢迎私信给我提出指导意见以及建议。
下面我解释一下我手写的 Testlib 各部分的作用和一些函数的意思:
函数 void registerTestlibCmd(int argc,char* argv[])
这个函数里初始化了文件的读入流。
因为要同时读入多个文件,所以我使用了 cstdio
库中的 fopen
函数。
函数的原型是 std::FILE* fopen( const char* filename, const char* mode );
。
具体使用方法可以参考 std::fopen - cppreference.com。
cassert
库中的 assert()
函数
在标头 <cassert>
定义:
#ifdef NDEBUG
#define assert(condition) ((void)0)
#else
#define assert(condition) /*implementation defined*/
#endif
如果传进该函数的参数为假,则会返回一个非 \(0\) 值(即 RE 段错误)。
cstdio
库中的 fscanf()
函数
在标头 <cstdio>
定义:
int fscanf( std::FILE* stream, const char* format, ... );//从文件流 stream 读数据,按照 format 转译并存储结果于给定位置。
上面这个函数的使用方法其实和 scanf()
差不多,只需要多加一个参数(即文件流)即可。
Special Judge 的对拍程序
Special Judge 的对拍程序
首先我们以 AT_dp_f LCS 这一道题为例。
首先将上面我手写的 testlib.h
保存(注:头文件不需要编译!)。
根据上面 SPJ 的编写方法,我们可以写出这样一个 checker.cpp
(请注意请和 testlib.h
放在同一个文件夹下方)并用 C++14 标准进行编译(最好开启无限栈):
//the code is from chenjh
#include "testlib.h"
#include<string>
#define WA quitf(_wa,"WA!")
#define AC quitf(_ok, "Correct.")
using namespace std;
string s,t,ou,jans;
int main(int argc,char* argv[]){
registerTestlibCmd(argc,argv);
s=inf.readToken();t=inf.readToken();
ou=ouf.readToken();jans=ans.readToken();
if(ou==jans){AC;return 0;}
if((int)ou.length()!=jans.length()){WA;return 0;}
int l=ou.length(),l1=s.length(),l2=t.length(),j=0;
for(int i=0;i<l;i++){
for(;ou[i]!=s[j] && j<l1;j++);
if(j>=l1 || ou[i]!=s[j]){WA;return 0;}
}
j=0;
for(int i=0;i<l;i++){
for(;ou[i]!=t[j] && j<l2;j++);
if(j>=l2 || ou[i]!=t[j]){WA;return 0;}
}
AC;
return 0;
}
接着把出现错误需要对拍的代码命名为 code.cpp
,正确的代码命名为 std.cpp
,并将它们都进行编译(不需要在代码中提前加入文件重定向)。
code.cpp
(这里选用了来自 @hky03118002 的 \(\color{#B3E600}{72}\) 分代码):
//the code is from hky(st20242008).
//Submission number is #502175.
#include<bits/stdc++.h>
using namespace std;
string ans[2][3010];
int f[2][3010],i,j,lena,lenb;
char a[3010],b[3010];
struct B{
int i,j,f;
}pre[3010][3010];
void print(int i,int j){
if(i<0||j<0||i==0&&j==0)return;
// printf("%d %d\n",i,j);
if(pre[i][j].i>0||pre[i][j].j>0)print(pre[i][j].i,pre[i][j].j);
if(pre[i][j].f)printf("%c",a[i]);
}
main(){
scanf("%s",a);
scanf("%s",b);
lena=strlen(a);
lenb=strlen(b);
for(i=0;i<lena;++i){
for(j=0;j<lenb;++j){
if(j>0&&f[(i&1)^1][j]<f[i&1][j-1]){
f[i&1][j]=f[i&1][j-1];
pre[i][j]={i,j-1};
}
else{
f[i&1][j]=f[(i&1)^1][j];
pre[i][j]={i-1,j};
}
if(a[i]==b[j]){
if(j>0&&f[i&1][j]<=f[(i&1)^1][j-1]+1){
f[i&1][j]=f[(i&1)^1][j-1]+1;
pre[i][j]={i-1,j-1,1};
}
else{
pre[i][j].f=1;
}
}
}
}
if(a[0]==b[0])printf("%c",a[0]);
print(i-1,j-1);
}
std.cpp
(选用了我的 \(\color{#00CC00}{100}\) 分代码):
//the code is from chenjh(c1120241702)
#include<bits/stdc++.h>
using namespace std;
char s1[3005],s2[3005];
int dp[3005][3005],p[3005][3005];
void print(int x,int y) {
if(x==0||y==0)return;
if(p[x][y]==1)
print(x-1,y-1),putchar(s1[x]);
else if(p[x][y]==-1)print(x-1,y);
else print(x,y-1);
}
int main(){
cin>>(s1+1)>>(s2+1);
int l1=strlen(s1+1),l2=strlen(s2+1);
for(int i=0;i<=l1;i++) dp[i][0]=0;
for(int i=0;i<=l2;i++) dp[0][i]=0;
for(int i=1;i<=l1;i++){
for(int j=1;j<=l2;j++){
if(dp[i-1][j]>=dp[i][j-1])p[i][j]=-1;
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
if(s1[i]==s2[j])
dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1),p[i][j]=1;
}
}
print(l1,l2);
// printf("%d\n",dp[l1][l2]);
return 0;
}
将制造数据的代码命名为 maker.cpp
然后进行编译(同样也不需要在代码中提前加入文件重定向)。
//the code is from chenjh
#include<cstdio>
#include<string>
#include<ctime>
#include<cstdlib>
using namespace std;
int rand(int l,int r){return 1ll*rand()*rand()%(r-l+1)+l;}
string lcs="";
int main(){
unsigned int *seed=new unsigned int;
srand(time(0)*(*seed+1));
delete seed;
int llen=rand(1,500);
for(int i=0;i<llen;i++) lcs+=(char)rand('a','z');
int nowlen=3000;
for(int i=0;i<llen;i++){
int mlen=rand(1,(3000-llen)/llen);
putchar(lcs[i]);
for(int j=0;j<mlen;j++) putchar(rand('a','z'));
nowlen-=mlen+1;
}
while(nowlen--) putchar(rand('a','z'));
putchar('\n');
nowlen=3000;
for(int i=0;i<llen;i++){
int mlen=rand(1,(3000-llen)/llen);
putchar(lcs[i]);
for(int j=0;j<mlen;j++) putchar(rand('a','z'));
nowlen-=mlen+1;
}
while(nowlen--) putchar(rand('a','z'));
return 0;
}
如果其中运用了随机数,并且种子和时间(例如 time(0)
相关)建议使用一个为初始化过的 int
类型的变量对时间进行相乘再设定为种子。
个人版对拍
接下来就是对拍程序(命名为 check.cpp
并进行编译,根据题目要求修改程序第 \(7\) 排的时间限制)了:
//the code is from chenjh
//用户自行配置:
//时间限制(单位为毫秒):
const int TimeLimit=2000;
#include<fstream>
#include<cstdio>
#include<ctime>
#include<cstdlib>
#include<string>
#include<windows.h>
using namespace std;
/*
Color:
需要 16 进制 0x 前缀。
0 = 黑色 8 = 灰色
1 = 蓝色 9 = 淡蓝色
2 = 绿色 A = 淡绿色
3 = 浅绿色 B = 淡浅绿色
4 = 红色 C = 淡红色
5 = 紫色 D = 淡紫色
6 = 黄色 E = 淡黄色
7 = 白色 F = 亮白色
*/
template<typename... Args>void COLOR_PRINT(const int front_color,const int back_color,const char*s,Args...x){
HANDLE handle=GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(handle, BACKGROUND_INTENSITY | back_color*16 | FOREGROUND_INTENSITY | front_color);
std::printf(s,x...);
SetConsoleTextAttribute(handle,FOREGROUND_INTENSITY|7);
}
bool fileExists(const std::string&filename){std::ifstream infile(filename);return infile.good();}//文件是否存在。
void SaveData(int &c){
string s="data\\data"+to_string(c++);
string str="copy data.in "+s+".in";
system(str.c_str());
str="copy data.ans "+s+".out";
system(str.c_str());
}
int main(){
system("md data");system("cls");
if(!fileExists("maker.exe")) return puts("Can\' t find file maker.exe!"),0;
if(!fileExists("checker.exe")) return puts("Can\' t find file checker.exe!"),0;
if(!fileExists("std.exe")) return puts("Can\' t find file std.exe!"),0;
if(!fileExists("code.exe")) return puts("Can\' t find file code.exe!"),0;
ofstream outvbs("KillCode.vbs");
outvbs<<"WSCript.Sleep "<<TimeLimit+200<<'\n';
outvbs<<"Set WshShell = WScript.CreateObject(\"WScript.Shell\")\nWshShell.Run \"taskkill /im code.exe /f\", 0, True\n";
outvbs.close();//用来关闭超时的进程。
int maxc;
printf("Enter the total number of pairs of shots you wish to obtain:");
scanf("%d",&maxc);//输入你想要的数据组数。
for(int t=1,c=1;c<=maxc;t++){
system("maker.exe > data.in");
system("std.exe < data.in > data.ans");
bool sd=0;
system("start /B KillCode.vbs");
double tm=clock();
unsigned int ls=system("code.exe < data.in > data.out");
tm=clock()-tm;
system("taskkill /f /im wscript.exe 1>nul 2>nul"),system("taskkill /f /im cscript.exe 1>nul 2>nul");
printf("\n\nTest#%d: ",t);
if(tm>TimeLimit) sd=1,COLOR_PRINT(0x7,0x1,"Time Limit Exceeded"),printf("! Time used %.0lf ms.\n",tm);
else if(ls>0xc0000000u) sd=1,COLOR_PRINT(0x7,0x5,"Runtime Error"),printf("! Time was unavailable!\n");//RE:3221225725
else if(ls) COLOR_PRINT(0x7,0x0,"Unknown Error!\n"),system("pause");
else{
unsigned int rs=system("checker.exe");
if(rs>0xc0000000u) COLOR_PRINT(0x7,0x5,"Checker Runtime Error"),printf("! Time was unavailable!\n"),system("pause");
else if(!rs) COLOR_PRINT(0x7,0xA,"Accepted"),printf("! Time used %.0lf ms.\n",tm);
else if(rs==1)sd=1,COLOR_PRINT(0x7,0x4,"Wrong Answer"),printf("! Time used %.0lf ms.\n",tm);
else COLOR_PRINT(0x7,0x0,"Unknown Error!\n"),system("pause");
}
if(sd) SaveData(c);
putchar('\n');
}
return 0;
}
运行效果图:
开始运行程序时输入一个整数(int
类型范围内)表示你需要多少组数据来 Hack 你的代码(提示:请勿作死输入一些很奇怪的数)。
上面的对拍程序会自动将你的代码 WA/TLE/RE 的测试数据(暂不支持判断内存是否超过内存限制)存至同文件夹下的 data
文件夹中。
团队版对拍
需要写一个 src.txt
,每一行为对拍程序的名称(不需要后缀名!),然后将所有代码保存到同文件夹下的 src
文件夹中。
不建议输入中文,因为会出现一些奇奇怪怪的编码错误。
//the code is from chenjh
#if __cplusplus < 201103L
#error This code must be enabled with the -std=c++11 or -std=gnu++11 compiler options.
#endif
#include<fstream>
#include<cstdio>
#include<ctime>
#include<cstdlib>
#include<string>
#include<vector>
#include<windows.h>
using std::string;
//用户自行配置:
//时间限制(单位为毫秒):
const int TimeLimit=2000;
//g++ 编译器路径:
const std::string g__="C:\\Program Files (x86)\\Dev-Cpp\\MinGW64\\bin\\g++.exe";
//编译选项:
const string cp=" -O2 -std=c++14 -Wl,--stack=2147483647" ;
//造数据器名称:
const string maker="maker";
//std 名称:
const string mystd="std";
//SPJ 名称 :
const string checker="checker";
/*
Color:
需要 16 进制 0x 前缀。
0 = 黑色 8 = 灰色
1 = 蓝色 9 = 淡蓝色
2 = 绿色 A = 淡绿色
3 = 浅绿色 B = 淡浅绿色
4 = 红色 C = 淡红色
5 = 紫色 D = 淡紫色
6 = 黄色 E = 淡黄色
7 = 白色 F = 亮白色
*/
template<typename... Args>void COLOR_PRINT(const int front_color,const int back_color,const char*s,Args...x){
HANDLE handle=GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(handle, BACKGROUND_INTENSITY | back_color*16 | FOREGROUND_INTENSITY | front_color);
std::printf(s,x...);
SetConsoleTextAttribute(handle,FOREGROUND_INTENSITY|7);
}
bool fileExists(const string&filename){std::ifstream infile(filename);return infile.good();}//文件是否存在。
void SaveData(int &c){
string s="data\\data"+std::to_string(c++);
system(("copy data.in "+s+".in").c_str());
system(("copy data.ans "+s+".out").c_str());
}
bool runcode(const string&username){
std::ofstream outvbs("KillCode.vbs");
outvbs<<"WSCript.Sleep "<<TimeLimit+200<<"\nSet WshShell = WScript.CreateObject(\"WScript.Shell\")\nWshShell.Run \"taskkill /im "<<username<<".exe /f\", 0, True\n";
outvbs.close();//用来关闭超时的进程。
system("start /B KillCode.vbs");
double tm=clock();
unsigned int ls=system(("src\\"+username+".exe < data.in > data.out").c_str());
tm=clock()-tm;
system("taskkill /f /im wscript.exe 1>nul 2>nul"),system("taskkill /f /im cscript.exe 1>nul 2>nul");
printf("\n%s: \n",username.c_str());
if(tm>TimeLimit) return COLOR_PRINT(0x7,0x1,"Time Limit Exceeded"),printf("! Time used %.0lf ms.\n",tm),1;
else if(ls>0xc0000000u) return COLOR_PRINT(0x7,0x5,"Runtime Error"),printf("! Time was unavailable!\n"),1;//RE:3221225725
else if(ls) COLOR_PRINT(0x7,0x0,"Unknown Error!\n"),system("pause");
else{
unsigned int rs=system((checker+".exe").c_str());
if(rs>0xc0000000u) COLOR_PRINT(0x7,0x5,"Checker Runtime Error"),printf("! Time was unavailable!\n"),system("pause");
else if(!rs) COLOR_PRINT(0x7,0xA,"Accepted"),printf("! Time used %.0lf ms.\n",tm);
else if(rs==1) return COLOR_PRINT(0x7,0x4,"Wrong Answer"),printf("! Time used %.0lf ms.\n",tm),1;
else COLOR_PRINT(0x7,0x0,"Unknown Error!\n"),system("pause");
}
return 0;
}
void comp(const string&filename){system(("start /B \"g++ compiler\" \""+g__+"\" "+filename+".cpp -o "+filename+".exe "+cp).c_str());}
std::vector<string> user;
int main(){
if(!fileExists("src.txt")) return puts("Can\' t find file src.txt!"),0;
puts("Read...");
std::ifstream fin("src.txt");
for(string us;!fin.eof();)getline(fin,us),user.push_back(us);
fin.close();
system("del /Q data 1>nul 2>nul");
system("md data 1>nul 2>nul");
puts("Compiling...");
if(!fileExists(maker+".cpp")) return puts(("Can\' t find file "+maker+".cpp!").c_str()),0;
system(("del /Q "+maker+".exe 1>nul 2>nul").c_str()),comp(maker);
if(!fileExists(checker+".cpp")) return puts(("Can\' t find file "+checker+".cpp!").c_str()),0;
system(("del /Q "+checker+".exe 1>nul 2>nul").c_str()),comp(checker);
if(!fileExists(mystd+".cpp")) return puts(("Can\' t find file "+mystd+".cpp!").c_str()),0;
system(("del /Q "+mystd+".exe 1>nul 2>nul").c_str()),comp(mystd);
for(const string&us:user){
if(!fileExists("src\\"+us+".cpp")) return printf("Can\' t find file src\\%s.cpp!\n",us.c_str()),0;
string sys="del /Q src\\"+us+".exe 1>nul 2>nul";
system(sys.c_str());
comp("src\\"+us);
Sleep(500);
}
while(system("wmic process where (name=\"g++.exe\") get ProcessId 2>nul | findstr /r \"[0-9]\" >nul"));
int maxc;
printf("Enter the total number of pairs of shots you wish to obtain:");
scanf("%d",&maxc);//输入你想要的数据组数。
char sys_make[100],sys_mystd[100];
strcpy(sys_make,(maker+".exe > data.in").c_str()),strcpy(sys_mystd,(mystd+".exe < data.in > data.ans").c_str());
for(int t=1,c=1;c<=maxc;t++){
system(sys_make);system(sys_mystd);
bool sd=0;
std::printf("Test %d:\n",t);
for(const string&us:user)sd|=runcode(us);
if(sd) SaveData(c);
putchar('\n');
}
return 0;
}
标签:std,const,int,system,return,Judge,编写,include,Special
From: https://www.cnblogs.com/Chen-Jinhui/p/17976205/About-Special-Judge