0%

(Update) Offline Judge 離線程式評測系統

前言

當初我同學有在架設我們學校的線上評測系統,而一開始我們的系統非常不穩定,因此,他想到可以寫離線的程式答題系統緩解我們上課的需求。而我看到這個系統,我覺得這是一個不錯的想法,並依此加以改良,新增 TLE 判定和超時時會強制停止,並且可以跨平台使用。

使用(解題者)

一題會是一個資料夾,裡面會有隱藏檔案和非隱藏檔案。

檔案名稱 大致內容 是否隱藏
AC ascii art 的 AC 字樣 True
WA ascii art 的 WA 字樣 True
TLE ascii art 的 TLE 字樣 True
main.h 主要程式 不用附給使用者
Run.cpp 執行程式 不用附給使用者
Run.exe 執行程式 False
Solve.cpp 使用者的程式 False
Description 題目敘述 False
README.md 說明使用方法(如下) False

README.txt 內文

以下說明檔案功能及用法

  1. 編譯並執行 Run.cpp 可以獲得評測結果
  2. 程式碼請寫在 Solve.cpp 裡面
  3. 題目敘述在 Description 裡面,一般應該是 .txt,.md 或 .pdf
  4. 標準輸入輸出用 cin 和 cout 就可以了

以下說明答題結果

  1. AC 作答正確
  2. WA 輸出結果錯誤,或不符合題目要求
  3. TLE 執行時間過長
  4. 程式跳掉 一般來說都是 RE(runtime error)

以下規定請勿違反

  1. 擅自更改除 Solve 以外的檔案

貼心小提醒

由於 Solve 函式會被重複使用,因此變數就算開在全域也要記得初始化(現版本已經不需要)

截圖

AC

使用(出題者)

資料夾中還含有一個隱藏資料夾 TestCase,裡面存放有測試資料。裡面的檔案如下

檔案名稱 大致內容
Gen.cpp 預設的測試資料生成程式
GraphGen.h 圖論測資生成程式 ( 使用介紹 )
Sol.cpp 官解檔
log.txt 測試資料們的基本資料

Gen.cpp

介紹各個函式的功能

函式 功能
RandomNumber(long long a,long long b) 生成一個隨機數 ( 範圍[a,b] )
RandomNumber(long long n) 生成一個隨機數 ( 範圍[1,n] )
solve() 共用解答函式(會調用Sol.cpp)
SubTesk1(int a) 生成第 a 筆測資

如果覺得 RandomNumber 太長,GraphGen.h 裡面有相同功能的函式 Rand

以下是 a+b problem 的範例。

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include<bits/stdc++.h>
#include "GraphGen.h"
using namespace std;

#define ALL(v) v.begin(),v.end()
using ll=long long;

unsigned seed=chrono::steady_clock::now().time_since_epoch().count();
mt19937_64 rng=mt19937_64(seed);

long long RandomNumber(long long a,long long b){
uniform_int_distribution<long long> dis(a,b);
return dis(rng);
}

long long RandomNumber(long long n){
return RandomNumber(1,n);
}

int solve(int i){
string file = "Sol / < " + to_string(i) + ".in" + "> " + to_string(i) + ".out";

int exec_status = system(file.c_str());
if (exec_status != 0) {
std::cerr << "Execution failed.\n";
return 1;
}

return 0;
}

void SubTesk1(int a){
string fileName=to_string(a);

ofstream ques(fileName+".in");

int aa=RandomNumber(10000),b=RandomNumber(10000);

ques<<aa<<" "<<b<<"\n";

cerr<<a<<endl;
}

void SubTesk2(int a){
string fileName=to_string(a);

ofstream ques(fileName+".in");

int aa=RandomNumber(10000),b=RandomNumber(10000);

ques<<aa<<" "<<b<<"\n";

cerr<<a<<endl;
}

#define REP(i,a,b) for(int i=(a);i<=(b);++i)
int main(){
ios::sync_with_stdio(0);cin.tie(0);

int compile_status = system("g++ Sol.cpp -o Sol");
if (compile_status != 0) {
std::cerr << "Compilation failed.\n";
return 1;
}

clock_t startTime=clock();
REP(i,1,5){
SubTesk1(i);
}

REP(i,6,10){
SubTesk2(i);
}

REP(i,1,10){
solve(i);
}

clock_t endTime=clock();

cerr<<double(endTime-startTime)/1000<<"\n";
getchar();
}

log.txt

只有三行,第一行是測試資料數目,第二行是執行時間限制,以毫秒為單位,第三行是題目ID。

1
2
3
TestCases:     5
TimeLimit(ms): 50
ProblemID: Default

主程式

函式 功能
RunCode(int timeLimit) 執行使用者程式(會計算時間,盡量減少多餘程式碼)
RunTestCase(int testCase,int timeLimit) 完成執行第 testCase 筆測試資料所需的前置條件
Judge(int testCase) 評判解答正確性
RunSolution() 執行所有測試並輸出結果
EnCode() AC時回傳密碼

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
#ifndef MAIN_H
#define MAIN_H

#include<bits/stdc++.h>
// #include<conio.h>
// #include "Solve.h"
using namespace std;

bool isfinish=false;
vector<int> costTime;
const int CODE_LENGTH=12;

class TimeLimitExceeded : public exception{
public :
TimeLimitExceeded() : exception() {}
};

class CompilationError : public exception{
public :
CompilationError() : exception() {}
};

long long RandomNumber(long long a,long long b,mt19937_64 &rng){
uniform_int_distribution<long long> dis(a,b);
return dis(rng);
}

long long RandomNumber(long long n,mt19937_64 &rng){
return RandomNumber(1,n,rng);
}

void RunCode(int timeLimit,int testCase){
isfinish=false;

string file =
"Sol / < ./TestCase\\" + to_string(testCase) + ".in" +
"> ./TestCase\\sol" + to_string(testCase) + ".out";

clock_t start=clock();

int exec_status = system(file.c_str());
if(exec_status != 0) {
throw runtime_error("");
return;
}

isfinish=true;

clock_t end=clock();

if(end-start>timeLimit){
throw TimeLimitExceeded();
return;
}

costTime.emplace_back(end-start);
return;
}

bool RunTestCase(int testCase,int timeLimit){
string fileNum=to_string(testCase);

// string inputFile="./TestCase\\";
// inputFile+=fileNum;
// inputFile+=".in";

// string userOutput="./TestCase\\sol";
// userOutput+=fileNum;
// userOutput+=".out";

// freopen(inputFile.c_str(),"r",stdin);
// freopen(userOutput.c_str(),"w",stdout);

thread run{RunCode,timeLimit,testCase};

clock_t start=clock();

while(!isfinish && clock()-start<=timeLimit);

// chrono::milliseconds s(timeLimit+20);
// this_thread::sleep_for(s);

if(!isfinish){
run.detach();
throw TimeLimitExceeded();
return false;
}else{
run.join();
return true;
}
}

bool Judge(int testCase){
ifstream userOutput("./TestCase\\sol"+to_string(testCase)+".out");
ifstream question("./TestCase\\"+to_string(testCase)+".in");
ifstream answer("./TestCase\\"+to_string(testCase)+".out");

string tp,userAns,systemAns;

while(userOutput>>tp){
userAns+=tp;
if(userAns.size()>10000000){
return false;
}
}

while(answer>>tp){
systemAns+=tp;
}

cerr<<" ";
return systemAns==userAns;
}

string Encode(){
ifstream fi("Solve.h");
string tp,file;
while(fi>>tp){
file+=tp;
}

int sum=0;
for(auto i:file){
sum+=(int)i;
}

mt19937_64 rng(sum);
string ret;

sum=0;
for(int i=0;i<CODE_LENGTH-1;++i){
int a=RandomNumber(100,rng);
sum+=a;
if(a<10) ret+="0";
ret+=to_string(a);
}

sum%=100;
if(sum==20){
ret+="00";
}else if(sum<20){
int a=20-sum;
if(a<10) ret+="0";
ret+=to_string(a);
}else{
int a=120-sum;
ret+=to_string(a);
}

return ret;
}

void RunSolution(){
ifstream log("./TestCase\\log.txt");

string recycle,problemID;
int testCases,timeLimit;

log>>recycle>>testCases;
log>>recycle>>timeLimit;
log>>recycle>>problemID;

ofstream output("output.info");

cerr<<"Problem ID : "<<problemID<<"\n";
cerr<<"There're "<<testCases<<" testcases."<<"\n";
cerr<<"Running TestCase..."<<"\n";

output<<"Problem ID : "<<problemID<<"\n";
output<<"There're "<<testCases<<" testcases."<<"\n";

try{
int compile_status = system("g++ Solve.cpp -O2 -o Sol");
if (compile_status != 0) {
std::cerr << "Compilation failed.\n";
throw CompilationError();
return;
}

for(int i=1;i<=testCases;++i){
RunTestCase(i,timeLimit);
cerr<<i<<" ";
}
cerr<<"\n\n";
}catch(TimeLimitExceeded){
ifstream TLE("TLE");
string line;
while(getline(TLE,line)){
cerr<<line<<"\n";
}
cerr<<flush;
getchar();
exit(0);
}catch(CompilationError){
ifstream CE("CE");
string line;
while(getline(CE,line)){
cerr<<line<<"\n";
}
cerr<<flush;
getchar();
exit(0);
}catch(exception){
ifstream RE("RE");
string line;
while(getline(RE,line)){
cerr<<line<<"\n";
}
cerr<<flush;
getchar();
exit(0);
}

bool allCorrect=true;
vector<bool> outputCorrect(testCases+1);

for(int i=1;i<=testCases;++i){
outputCorrect[i]=Judge(i);
if(!outputCorrect[i]){
allCorrect=false;
}
}

cerr<<"\n";
if(allCorrect){
ifstream AC("AC");
string line;
while(getline(AC,line)){
cerr<<line<<"\n";
output<<line<<"\n";
}
cerr<<flush;
}else{
ifstream WA("WA");
string line;
while(getline(WA,line)){
cerr<<line<<"\n";
output<<line<<"\n";
}
cerr<<flush;
}

string ret[2]{"WA","AC"};
cerr<<"For each testcase : "<<"\n\n";
output<<"For each testcase : "<<"\n\n";
for(int i=1;i<=testCases;++i){
cerr<<right<<setw(3)<<i<<". "<<flush;
cerr<<ret[outputCorrect[i]]<<" "<<flush;
cerr<<"Execution time : "<<right<<setw(4)<<costTime[i-1]<<" ms"<<endl;
output<<right<<setw(3)<<i<<". "<<flush;
output<<ret[outputCorrect[i]]<<" "<<flush;
output<<"Execution time : "<<right<<setw(4)<<costTime[i-1]<<" ms"<<endl;
}
cerr<<endl;
output<<endl;

if(allCorrect){
string code=Encode();
cerr<<"AC code : "<<code<<endl;
output<<"AC code : "<<code<<endl;
}

getchar();
}

#endif

競賽中不會出現的 C++ 功能

多執行續 thread 函式庫

1
2
3
4
5
6
7
8
// 開新的執行續處理函式 RunCode
thread run{RunCode,timeLimit};
// 讓此執行續等待一段時間 chrono 格式
this_thread::sleep_for(chrono::milliseconds(timeLimit+20))
// 放空該執行續(與物件 run 取消綁定,但會繼續執行)
run.detach();
// 合併執行續,意味著它會等待到 run 那個執行序完成(return)後再合併
run.join();

其它題目

可以去雲端硬碟下載。這裡有一些題目。Mega

電子簽章

1
signtool.exe sign /a /fd SHA256 /td SHA256 /tr http://timestamp.comodoca.com "xxx.exe"
signtool.exe

延伸閱讀

Offline Judge 離線程式評測系統