• 如何给LOJ补全special judge


    首先你要会写一个叫$data.yml$的东西,

    这里面记录了这道题的$subtask$计分策略

    也告诉了评测姬这道题是提交答案还是$spj$还是交互题

    那么,$YAML$语言是啥啊?

    别问我,我也不会 本着会用能用就行的原则

    给大家讲讲$LOJ$的$special jidge$怎么用

    $yaml$里基本上的全部语法:

    $Structure$通过空格来展示。$Sequence$里的项用$-$来代表,Map里的键值对用$:$分隔.

    在$LOJ$我们需要写一份类似这样的$data.yml$文件:

    (摘自 LOJ帮助 ,稍有改动)

    subtasks:           # 定义subtask
      - score: 30              # 这个子任务的分数(注意,所有子任务的总分必须为 100)
        type: sum              # 子任务类型,可选的值有 sum、min 和 mul.
        cases: [1, 2, 3]       # 测试点编号可为数字
      - score: 30              # 另一个子任务
        type: mul        #sum:求和 mul:求积之后折合回100 min:取最小值按百分比折合 
        cases: ['4', '5', '6'] # 测试点编号也可为字符串
    inputFile: 'dat#.in' # 告诉评测姬你测试数据包中的输入文件 outputFile: 'dat#.ans' # 告诉评测姬你测试数据包中的输出文件 # 上述文件名中的 '#' 字符将被替换为测试点编号,eg:dat1.in,dat1.out # Special Judge 可省略 specialJudge: language: cpp      #语言,这里用简称  fileName: spj.cpp    #这里是你 答案检查器 的名子,后缀最好要和language一样(没试过不一样行不行)

    语言简称:ccppcpp11csharphaskelljavalualuajitnodejspascalpython2python3rubyvalavbnetocam

    这里开始举个栗子,$A+B problem$ ,$10$个测试点,为$data1.in,data1.out ~ data10.in,data10.out$。

    然后

      对于30%的数据,a,b<=100;

      对于另30%的数据,a,b<=1000000;

      对于另40%的数据,a,b<=100000000000000000000000000000000000000000000000000000000000000000;

     这时候,你的$data.yml$里就要这样写:

    subtasks:
      - score: 30
        type: sum
        cases: [1, 2, 3]
      - score: 30
        type: mul
        cases: [4, 5, 6]
      - score: 40
        cases: [7, 8, 9, 10]
    
    inputFile: 'data#.in'
    outputFile:  'data#.out'

    然后对于某沙华大佬的那道题,需要有$spj$

    subtasks:
      - score: 12
        type: sum
        cases: ['01-14','02-1234','03-1234','04-14','05-1234','06-1234','07-1234','08-1234','09-14','10-1234','11-134','12p-1234','23p-1234']
      - score: 15
        type: sum
        cases: ['02-1234','03-1234','05-1234','06-1234','07-1234','08-1234','10-1234','12p-1234','13-234','14-234','15-234','16-234','17-234','18-234','19-234','20-234','21-234','22-234','23p-1234','24-234','25-234','26-234','27-234','28-234','29-234','30-234','31-234','32-234','33-234','34-234']
      - score: 23
        type: sum
        cases: ['02-1234','03-1234','05-1234','06-1234','07-1234','08-1234','10-1234','11-134','12p-1234','13-234','14-234','15-234','16-234','17-234','18-234','19-234','20-234','21-234','22-234','23p-1234','24-234','25-234','26-234','27-234','28-234','29-234','30-234','31-234','32-234','33-234','34-234','35-34','36-34','37-34','38-34','39-34','40-34','41-34','42-34']
      - score: 50
        type: sum
        cases: ['01-14','02-1234','03-1234','04-14','05-1234','06-1234','07-1234','08-1234','09-14','10-1234','11-134','12p-1234','13-234','14-234','15-234','16-234','17-234','18-234','19-234','20-234','21-234','22-234','23p-1234','24-234','25-234','26-234','27-234','28-234','29-234','30-234','31-234','32-234','33-234','34-234','35-34','36-34','37-34','38-34','39-34','40-34','41-34','42-34','43-4','44-4','45-4','46-4','47-4','48-4','49-4','50-4','51-4','52-4','53-4','54-4','55-4','56-4','57-4','58-4','59-4','60-4','61-4','62-4','63-4','64-4','65-4','66-4']
    
    inputFile: 'demarcation.#.in'
    outputFile: 'demarcation.#.sol'
    
    specialJudge:
      - language: cpp11
        fileName: spj_cpp11.cpp
     - language: cpp
       fileName: checker.cpp

    (嘤嘤嘤这个数据点编号我也是醉了qwq)

    下面开始讲讲怎么写$special judge$

    只会C++

    $Special Judge$ 程序运行时,其目录下会有四个文件 inputuser_outanswercode,分别对应该测试点的输入文件、用户输出、该测试点的输出文件、用户的代码(对于非提交答案题目)。

    你可以从这四个文件里读入东西……

    $Special Judge$ 程序运行完成后,应将该测试点的得分输出到标准输出(stdout)中(范围为 0100,将自动折合为测试点分数),并将提供给用户的额外信息输出到标准错误输出(stderr)中。

    然后好像……

    LOJ并不支持 $testlib.h$

    $mdzz$

    (本宝宝内心是拒绝给一个没$testlib.h$的OJ写$spj$的)

    既然不支持,由于今天主要讲的是$LOJ$的$spj$写法

    那我哭着也要写完吧……

    上面说了,我们可以对那4个文件干一些事情

     所以我这里只介绍输入输出怎么整,并不介绍运算(因为判断合法性要相对于题而言,并不能概括)

    void read(char *fileName)
    {
        FILE *f = fopen(fileName, "r");
        fscanf(f,...,...);
        fclose(f);
    }

     调用这个函数的意思是:

    从名为$fileName$的文件里读取一些东西

    其中,

    $fscanf(  )$里面先要有个$f,$之后就是$scanf$的格式。

    之后就是给出正确、错误状态以及得分

    void writ(const char s[],int point) {
    //给出答案错误以及得分
    /*
    根据LOJ帮助里说,得分为stdout,详情为stderr
    于是就用fprintf()来输出stderr,printf来输出得分
    参数中,char数组表示的是详细信息的一个字符串,point是得分
    */
        fprintf(stderr, "%s
    ", s);
        printf("%d
    ",point);
        exit(0);
    }

    然后就基础语法了

    放上一波头文件

    #include<cstdio>
    #include<fstream>
    #include<cstdlib>
    #include<vector>
    #include<cstring>
    #include<cctype>
    #include<algorithm>
    using namespace std;

    如果你不会写$data.yml$,但又想要$spj$,你需要在文件里包含一个 spj_***.xxx

    其中,$***$为语言简称,$xxx$为任意后缀(强烈安利$.qwq$文件)

    下面给出$a+b problem$的栗子:

    #include<cstdio>
    #include<fstream>
    #include<cstdlib>
    #include<vector>
    #include<string>
    #include<cctype>
    #include<algorithm>
    using namespace std;
    int std_ans;
    int per_out;
    void read_ans(char *fileName)//从文件里读入一个数,存到std_ans里 
    {
        FILE *f = fopen(fileName, "r");
        fscanf(f,"%d",&std_ans);
        fclose(f);
    }
    void read_out(char *fileName)//从文件里读入一个数,存到per_out里 
    {
        FILE *f = fopen(fileName, "r");
        fscanf(f,"%d",&per_out);
        fclose(f);
    }
    void writ(const char s[],int point) {
        fprintf(stderr, "%s
    ", s);
        printf("%d
    ",point);
        exit(0);
    }
    int main()
    {
        read_out("user_out");//从user_out,用户输出中读取per_out 
        read_ans("answer");//从 该测试点的输出文件 中读取 std_ans
        if(std_ans==per_out) writ("ok the answer is corrit",100);//返回正确
        else writ("you are wrong",0);//返回错误 
    } 

    到这里,基础教程就没了,高级的奇技淫巧还在后面……


    你有没有发现,如果你要读一堆东西,发现你要写好多$read()$函数……

    于是就崩溃了啊。

    那怎么办呢?

    魔改一下就好啊

    int read_int(char *fileName)//从文件里读入一个数,返回这个数的值 
    {
        FILE *f = fopen(fileName, "r");
        int now;
        fscanf(f,"%d",&now);
        fclose(f);
        return now;
    }

    这样我们就少写了好多东西哎

    此时的$a+b$少了一个函数:

    #include<cstdio>
    #include<fstream>
    #include<cstdlib>
    #include<vector>
    #include<string>
    #include<cctype>
    #include<algorithm>
    using namespace std;
    int std_ans;
    int per_out;
    int read_int(char *fileName)
    {
        FILE *f = fopen(fileName, "r");
        int now;
        fscanf(f,"%d",&now);
        fclose(f);
        return now;
    }
    void writ(const char s[],int point) {
        fprintf(stderr, "%s
    ", s);
        printf("%d
    ",point);
        exit(0);
    }
    int main()
    {
        per_out=read_int("user_out");
        std_ans=read_int("answer");
        if(std_ans==per_out) writ("ok the answer is corrit",100);
        else writ("you are wrong",0);
    } 

    哇……这样我们是不是可以类比呢?

    没错

    char read_char(char *fileName)
    {
        FILE *f = fopen(fileName, "r");
        char now;
        fscanf(f,"%c",&now);
        fclose(f);
        return now;
    }

    当然,下面这个慎用……

    下面这个慎用!!!

    前面加 $#include<iostream>$

    string resd_string(char *fileName)
    {
        freopen(fileName,"r",stdin);
        string now;
        cin>>now;
        fclose(stdin);
        return now;
    }

     这样是好写了……但是别忘了

    $checker.cpp$运行也是有时间限制的啊啊啊、

    所以……

    你读一个数运行一次$fopen,fclose$可能会崩

    所以你可以写一个指定从某文件读取$int,char$的函数。

    这里我就不写了$qwq$


     哇啊啊啊神tm LOJ支持$testlib.h$

    那么就来讲讲$testlib.h$怎么用吧

    首先我们需要有这个$testlib.h$

    下载地址:戳这里

    然后呢,我们需要的是

    使用自定义校验器,请在problem.conf中去掉use_builtin_checker那一行。

    (其实没什么用)

    开始介绍$testlib.h$中的语法

    首先必要的几个东西:

    #include "testlib.h"
    int main(int argc,char *argv[])
    {
        registerTestlibCmd(argc, argv);
       /*    
        your main function
        */ 
        return 0;
    }

    第四行那句话是必须要加的,所谓的初始化$qwq$

    不过,偷偷的告诉你,$testlib.h$里面包含的头文件:

    #define random __random_deprecated
    #include <stdlib.h>
    #include <cstdlib>
    #include <climits>
    #include <algorithm>
    #undef random
    
    #include <cstdio>
    #include <cctype>
    #include <string>
    #include <vector>
    #include <map>
    #include <set>
    #include <cmath>
    #include <sstream>
    #include <fstream>
    #include <cstring>
    #include <limits>
    #include <stdarg.h>
    #include <fcntl.h>

    所以基本上不用$#include$其他的东东。

     之后,支持testlib.h$,就不用管之前的选手答案等东西的文件名了

    $testlib.h$里面自带的文件名有这几个:

    • inf输入数据
    • ouf选手输出
    • ans标准输出

    所以我们调用的话,直接$inf.***$ $ouf.***$ $ans.**$ 就好

    下面说语法:

    首先是读取

    读取方法:

    char readChar()

    读入一个char。

    char readChar(char c)

    和上面一样,但是只能读到一个特定字母c

    char readSpace()

    读取一个空格同 readChar(' ').

    string readToken()

    读入一个字符串,但是遇到空格、换行、eof为止、

    long long readLong()

    读入一个$long long$

    long long readLong(long long L, long long R)

    同上,但是限定范围(包括L,R)

    int readInt()

    读入一个$int$

    int readInt(int L, int R),

    同上,但是限定范围(包括L,R)`

    double readReal()

    读入一个实数

    double readReal(double L, double R),

    同上,但是限定范围(包括L,R)

    double readStrictReal(double L, double R, int minPrecision, int maxPrecision),

    读入一个限定范围精度位数的实数。

    string readString(),

    string readLine()

    读取一行string,到换行或者eof为止

    void readEoln()

    读入一个换行符

    void readEof()

    读入一个$eof$

    int eof()

    然后怎么调用这些呢?

    我们从想要的文件里读入一个想要的类型的变量

    就用$###.***$其中,$###$是文件名(上述三个),$***$是函数名(刚刚说的一堆)

    举个例子,我们要从选手输出文件读取一个$int$

    这么写就行了

    int per_out=ouf.readInt();

    这样就会从选手文件里读入一个$int$存到$per_out$里。

    然后就是给出得分和答案

    和$fprintf$不是一般的像,

    这里用到的叫$qutif$和$quitp$函数,最少支持1个参数,最多……呵呵

    格式:

    $quitf(***,string,...)$

    首先第一个参数是必须有的

    $quitf$可选的值为:

    1.    ok  //满分
    2.    wa  //Wown answer ,0分
    3.    double变量 //给选手a*100的分 (0<=a<=1)
    4.    ceil(100.0*p/a)/100    //给选手p分

    中的$1,2$

    $quitp$的第一个参数可以填3$,4$

    quitf(\_ok, "The answer is correct. ");

    给出AC

    quitf(\_wa, "The answer is wrong");

    给出WA

    quitp(0.5,"Partially Correct");

    给出$50\%$的分

    quitp(ceil(100.0 * p / a) / 100, "QAQ");

    这将会给选手p分。

    之后那个字符串就是要显示的详情,里面遵循$printf$语法,就是支持 $\%d$等东西

    然后后面跟着几个要显示的内容

    这里口糊不太清楚,直接上代码

     $a+b$的$spj$就可以这么写:

    ps:这个直接从简介里抄了一份,请见谅

    #include "testlib.h"
    int main(int argc, char* argv[])
    {
        registerTestlibCmd(argc, argv);
        int pans,jans;
        pans=ouf.readInt();
        jans=ans.readInt();
        if (pans == jans)
            quitf(_ok, "Correct. the answer is: %d 
     QAQ " , jans);
        else
            quitf(_wa, "WA! expect=%d recieve=%d", jans, pans);
    }

     之后,多看几份$spj$就能自己写一个不错的$spj$了

    然后呢……

    下面奉上一份某沙华大佬的$spj$

    额……某沙华大佬表示已经没题了

    不,是沙华不会写QWQ

     (强烈吐槽一句:LOJ的压榨翻译组的人力工作qwq)

  • 相关阅读:
    未在本地计算机上注册“Microsoft.Jet.OleDb.4.0”提供程序
    GridView选中行变色
    GridView 添加/删除行
    Session、Cookie、Application、ViewState和Cache 这四者的区别
    后台动态给textbox的字体颜色赋值
    JS来判断文本框内容改变事件
    gridview JS控件赋值后如何取值
    c#如何把8位字符串转换成日期格式
    从今天起,热爱生活
    杂得得杂
  • 原文地址:https://www.cnblogs.com/arcturus/p/9501345.html
Copyright © 2020-2023  润新知