Skip to content

Commit

Permalink
对 Special Judge 引入 ,该功能会导致与旧有的 Special Judge 写法不兼容
Browse files Browse the repository at this point in the history
  • Loading branch information
shi-yang committed Mar 1, 2019
1 parent 1ff86d2 commit 33fe402
Show file tree
Hide file tree
Showing 9 changed files with 4,826 additions and 49 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -12,6 +12,7 @@ JNOJ Change Log
- Enh: [web] 添加 VIP 用户权限,可以将题目设为只有 VIP 用户可见
- Bug: [web] 修复 VIP 题目查询问题
- Bug: [web] 编辑私有题目时无法保存
- Enh: [web、judge] 对 Special Judge 引入 `testlib.h`,该功能会导致与旧有的 Special Judge 写法不兼容,参考OJ 的 Wiki。

0.7.0 2019.2.1
------------------------
Expand Down
2 changes: 1 addition & 1 deletion judge/src/judge.c
Expand Up @@ -795,7 +795,7 @@ int special_judge(char* oj_home, int problem_id, char *infile, char *outfile,
setrlimit(RLIMIT_FSIZE, &LIM);

ret = execute_cmd("%sdata/%d/spj '%s' '%s' %s", oj_home, problem_id,
infile, outfile, userfile);
infile, userfile, outfile);
if (ret)
exit(1);
else
Expand Down
4,681 changes: 4,681 additions & 0 deletions libraries/testlib.h

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion modules/admin/controllers/ProblemController.php
Expand Up @@ -330,7 +330,7 @@ public function actionSpj($id)
$fp = fopen($dataPath . '/spj.cc',"w");
fputs($fp, $spjContent);
fclose($fp);
exec("g++ {$dataPath}/spj.cc -o {$dataPath}/spj");
exec("g++ -fno-asm -std=c++11 -O2 {$dataPath}/spj.cc -o {$dataPath}/spj -I" . Yii::getAlias('@app/libraries'));
}

return $this->render('spj', [
Expand Down
2 changes: 1 addition & 1 deletion modules/polygon/controllers/ProblemController.php
Expand Up @@ -136,7 +136,7 @@ public function actionSpj($id)
$fp = fopen($dataPath . '/spj.cc',"w");
fputs($fp, $model->spj_source);
fclose($fp);
@exec("g++ {$dataPath}/spj.cc -o {$dataPath}/spj");
exec("g++ -fno-asm -std=c++11 -O2 {$dataPath}/spj.cc -o {$dataPath}/spj -I" . Yii::getAlias('@app/libraries'));
return $this->redirect(['spj', 'id' => $model->id]);
}
return $this->render('spj', [
Expand Down
2 changes: 1 addition & 1 deletion polygon/src/judge.c
Expand Up @@ -781,7 +781,7 @@ int special_judge(char* oj_home, int problem_id, char *infile, char *outfile,
setrlimit(RLIMIT_FSIZE, &LIM);

ret = execute_cmd("%sdata/%d/spj '%s' '%s' %s", oj_home, problem_id,
infile, outfile, userfile);
infile, userfile, outfile);
if (ret)
exit(1);
else
Expand Down
1 change: 1 addition & 0 deletions views/layouts/wiki.php
Expand Up @@ -15,6 +15,7 @@
['label' => Yii::t('app', 'OJ 信息'), 'url' => ['wiki/index']],
['label' => Yii::t('app', 'Contest'), 'url' => ['wiki/contest']],
['label' => '出题要求', 'url' => ['wiki/problem']],
['label' => Yii::t('app', 'Special Judge'), 'url' => ['wiki/spj']],
['label' => Yii::t('app', 'About'), 'url' => ['wiki/about']]
],
'options' => ['class' => 'nav nav-pills nav-stacked']
Expand Down
46 changes: 1 addition & 45 deletions views/wiki/problem.php
Expand Up @@ -72,51 +72,7 @@

<h3>Special Judge</h3>
<p>简称 SPJ,这是针对用户输出的特判。比如,根据题面求解出来的答案可能存在多个,这样就无法定义一个准确的输出文件来判断用户是否正确,这时就需要 SPJ。或者允许用户的输出在某一精度范围内是正确的。</p>
<?php Modal::begin([
'header' => '<h2>SPJ 模板示例</h2>',
'toggleButton' => ['label' => 'SPJ 模板示例', 'class' => 'btn btn-success'],
]) ?>
<p>1. SPJ 是一个可执行程序,其的返回值决定着判断结果,成功返回(0)表示AC,其他非零值表示WA。</p>
<p>2. SPJ 放在与测试数据同一目录下,文件名为 <code>spj</code>(小写,这个名字不能错),需要有执行权限,判题时会自动调用 SPJ。
对于题库中,SPJ 处于 judge/data/problemId 目录下,problemId 对应于题目的ID;对于 polygon 中,SPJ 处于 polygon/data/problemId 目录下</p>
<p>3. 在后台创建题目时,填写的 SPJ 源码会自动编译成可执行程序并放在相应的目录下。</p>
<p>4. 使用 polygon 来创建题目,导入题库时会自动编译成可执行程序。</p>
<p class="text-danger">5. 当前未对 SPJ 的可调用函数进行限制,故在 polygon 中验题功能不会开放,需要拉到题库后再从后台进行验题。</p>
<p>6. 针对第 5 条,请确保 SPJ 正确运行,也未调用与判题无关的系统函数,当 SPJ 在 OJ 中编译出错或运行出错时,OJ 不会给出反馈。</p>
<div class="pre"><p>#include &lt;stdio.h&gt;
#define AC 0
#define WA 1
const double eps = 1e-4;
int main(int argc,char *args[])
{
FILE * f_in = fopen(args[1],"r");
FILE * f_out = fopen(args[2],"r");
FILE * f_user = fopen(args[3],"r");
int ret = AC;
/**************判题逻辑**************/
/**
* 以下判题逻辑代码只是举例:输入有 t 组数据,写 spj 来判断每组数据测试输出与用户输出之差是否在 eps 之内。
*/
int t;
double a, x;
fscanf(f_in, “%d”, &t); //从输入中读取数据组数 t
while (t-–) {
fscanf(f_out, “%lf”, &a); //从读取测试输出
fscanf(f_user, “%lf”, &x); //从读取用户输出
if(fabs(a-x) > eps) {
ret = WA;//Wrong Answer
break;
}
}
/***********************************/
fclose(f_in);
fclose(f_out);
fclose(f_user);
return ret;
}
</p></div>
<?php Modal::end() ?>

<p>写法详见:<?= Html::a('SPJ', ['/wiki/spj']) ?></p>

<h3 id="infile">如何快速生成输入文件</h3>
<p>以下只是提供 C 语言一种示范,<b>数据请勿完全依赖随机生成,应根据题目要求考虑不同情况设定数据,同时应包含题面各个范围的数据情况。</b></p>
Expand Down
138 changes: 138 additions & 0 deletions views/wiki/spj.php
@@ -0,0 +1,138 @@
<h2>Special Judge</h2>
<hr>
<p>简称 SPJ,这是针对用户输出的特判。比如,根据题面求解出来的答案可能存在多个,这样就无法定义一个准确的输出文件来判断用户是否正确,这时就需要 SPJ。或者允许用户的输出在某一精度范围内是正确的。</p>

<p>SPJ 是一个用CC++写的可执行程序,其的返回值决定着判断结果,成功返回(0)表示AC,其他非零值表示WA。</p>
<p>SPJ 的编译参数为:g++ -fno-asm -std=c++11 -O2 ,即已经开启C++11以及O2优化。</p>
<p>请确保 SPJ 程序的正确运行,也<b>未调用与判题无关的系统函数</b>,当 SPJOJ 中编译出错或运行出错时,OJ 不会给出反馈。</p>

<hr>
<h3>示例一:</h3>
<div class="pre"><p>#include &lt;stdio.h&gt;
#define AC 0
#define WA 1
const double eps = 1e-4;
int main(int argc,char *args[])
{
FILE * f_in = fopen(args[1],"r");
FILE * f_user = fopen(args[2],"r");
FILE * f_out = fopen(args[3],"r");
int ret = AC;
/**************判题逻辑**************/
/**
* 以下判题逻辑代码只是举例:输入有 t 组数据,写 spj 来判断每组数据测试输出与用户输出之差是否在 eps 之内。
*/
int t;
double a, x;
fscanf(f_in, “%d”, &t); //从输入中读取数据组数 t
while (t-–) {
fscanf(f_out, “%lf”, &a); //从读取测试输出
fscanf(f_user, “%lf”, &x); //从读取用户输出
if(fabs(a-x) > eps) {
ret = WA;//Wrong Answer
break;
}
}
/***********************************/
fclose(f_in);
fclose(f_out);
fclose(f_user);
return ret;
}
</p></div>

<hr>
<h3>示例二:</h3>

<p>当前 OJ 采用了跟 Codeforces 一样的 SPJ 标准,即Testlib库。</p>

<p>下载地址:
<a href="https://github.com/MikeMirzayanov/testlib">
https://github.com/MikeMirzayanov/testlib
</a>
</p>

<p>当标准输出和选手输出的差小于0.01,那么可以AC,否则WA。</p>
<div class="pre"><p>#include "testlib.h"
int main(int argc, char* argv[]) {
registerTestlibCmd(argc, argv);
double pans = ouf.readDouble();
double jans = ans.readDouble();

if (fabs(pans - jans)<0.01)
quitf(_ok, "The answer is correct.");
else
quitf(_wa, "The answer is wrong: expected = %f, found = %f", jans, pans);
}
</p></div>

<p>在程序中,有3个重要的结构体:inf指数据输入文件(本例没有),ouf指选手输出文件,ans指标准答案。</p>

<p>然后,可以从这3表结构体读入数据,不需要用到标准输入输出。如果读到的数据和下面的期望不一致,则spj返回fail结果。</p>

<p>这边继续给出一个多行(不定行数)的spj判断:</p>
<div class="pre"><p>#include "testlib.h"
int main(int argc, char* argv[]) {
registerTestlibCmd(argc, argv);

while(!ans.eof()){
double pans = ouf.readDouble();
double jans = ans.readDouble();
ans.readEoln();

if (fabs(pans - jans)>0.01)
quitf(_wa, "The answer is wrong: expected = %f, found = %f", jans, pans);
}
quitf(_ok, "The answer is correct.");
return 0;
}
</p></div>

<p>以下读入命令可以使用:</p>

<p>初始化checker,必须在最前面调用一次:<code>void registerTestlibCmd(argc, argv)</code></p>

<p>读入一个char,指针后移一位:<code>char readChar()</code></p>

<p>和上面一样,但是只能读到一个字母c:<code>char readChar(char c)</code></p>

<p>同 readChar(' '):<code>char readSpace()</code></p>

<p>读入一个字符串,但是遇到空格、换行、eof为止:<code>string readToken()</code></p>

<p>读入一个long long/int64:<code>long long readLong()</code></p>

<p>同上,但是限定范围(包括LR):<code>long long readLong(long long L, long long R)</code></p>

<p>读入一个int:<code>int readInt()</code></p>

<p>同上,但是限定范围(包括LR):<code>int readInt(int L, int R)</code></p>

<p>读入一个实数:<code>double readReal()</code></p>

<p>同上,但是限定范围(包括LR):<code>double readReal(double L, double R)</code></p>

<p>读入一个限定范围精度位数的实数:<code>double readStrictReal(double L, double R, int minPrecision, int maxPrecision)</code></p>

<p>读入string,到换行或者eof为止:<code>string readString(), string readLine()</code></p>

<p>读入一个换行符: <code>void readEoln()</code></p>

<p>读入一个eof:<code>void readEof()</code></p>

<p>输出:</p>

<p>给出 AC:<code>quitf(\_ok, "The answer is correct. answer is %d", ans);</code></p>

<p>给出 WA:<code>quitf(\_wa, "The answer is wrong: expected = %f, found = %f", jans, pans);</code></p>

<hr>
<h3>测试</h3>

使用编译器将该文件编译。在命令行中输入:
<div class="pre"><p>./spj in.txt out.txt ans.txt # Linux
spj.exe in.txt out.txt ans.txt # Windows
</p></div>
其中in.txt out.txt ans.txt分别是放在同一目录下的输入文件、选手输出、标准答案。

程序将返回结果。

0 comments on commit 33fe402

Please sign in to comment.