测试点地图题怎么配

· · 休闲·娱乐

测试点地图题,顾名思义就是用测试点画出一些图案,通过这些图案来做的题,例如 Escape's Metabox,Game。

由于这种题目不是什么正经题因此投稿至【休闲·娱乐】。虽然但是这篇文章也是比较干货的。

先看题目:https://www.luogu.com.cn/problem/U682442

:::info[提交记录示例]{open} AC 记录:https://www.luogu.com.cn/record/276130881

在出口右边两格:https://www.luogu.com.cn/record/276130658

在起点:https://www.luogu.com.cn/record/276130956

步数超限:https://www.luogu.com.cn/record/276203686 :::

用到的关键代码有好几个,全都放在下面的折叠框里。

:::info[checker.cpp(题目的 SPJ)]

#include"testlib.h"
using namespace std;
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))
const int N=2e6+10,mod=1e9+7;
char g[20][20]={"",
    " ###############",
    " #.#...#...#...#",
    " #.#.#.#.#.#.#.#",
    " #...#...#...#.#",
    " #.###.###.###.#",
    " #.#...#...#...#",
    " #.#.#.###.#.#.#",
    " #.#.#...#.#.#.#",
    " #.#.#.#.#.#.#.#",
    " #.#...#.#...#.#",
    " #.###.#.#.###.#",
    " #...#.#.#.#...#",
    " #.###.#.#.###.#",
    " #.#...#...#...#",
    " ###############"
};
signed main(signed argc,char** argv)
{
    registerTestlibCmd(argc,argv);
    int x=14,y=2,ex=14,ey=12;
    int c=0;
    if(!ouf.seekEof())
    {
        string s=ouf.readLine();
        for(char ch:s)
        {
            int fx=x,fy=y;
            if(ch=='w') fx--,c++;
            if(ch=='a') fy--,c++;
            if(ch=='s') fx++,c++;
            if(ch=='d') fy++,c++;
            if(g[fx][fy]!='#') x=fx,y=fy;
        }
    }
    if(c>=1000) quitp(0,"Too many steps!");
    if(x==ex&&y==ey) quitp(10*1000+c,"You found the exit!!!");
    int px=x,py=y;
    while(px<=3) px++;
    while(px>=13) px--;
    while(py<=3) py++;
    while(py>=13) py--;
    int tx=ans.readInt(),ty=ans.readInt();
    tx=px+tx-4,ty=py+ty-4;
    if(tx==x&&ty==y) quitp(1*1000+c,"Player");
    if(tx==ex&&ty==ey) quitp(2*1000+c,"Exit");
    if(g[tx][ty]=='#') quitp(3*1000+c,"Wall");
    quitp(4*1000+c,"Empty space");
    return 0;
}

::: :::info[datagen.cpp(生成数据)]

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))
const int N=2e6+10,mod=1e9+7;
signed main()
{
    for(int i=1;i<=49;i++)
    {
        string s="data"+to_string(i)+".in";
        freopen(s.c_str(),"wb",stdout);
        s="data"+to_string(i)+".out";
        freopen(s.c_str(),"wb",stdout);
        cout<<(i-1)/7+1<<' '<<(i-1)%7+1<<'\n';
    }
    return 0;
}

::: :::info[configgen.cpp(生成 config.yml)]

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))
const int N=2e6+10,mod=1e9+7;
signed main()
{
    freopen("config.yml","w",stdout);
    for(int i=1;i<=49;i++)
    {
        cout<<"data"<<i<<".in:\n";
        cout<<"    score: 1\n\n";
    }
    return 0;
}

::: ::::info[evalgen.cpp(生成自定义计分脚本)]

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))
const int N=2e6+10,mod=1e9+7;
signed main()
{
    freopen("evalscript.txt","w",stdout);
    cout<<"@final_time=@score1 % 1000;\n";
    cout<<"if @score1>=10000; then\n@total_score=2147483647;\n@final_status=AC;\nelse\n";
    for(int i=1;i<=49;i++)
    {
        cout<<"if @score"<<i<<">=1000; then\n@status"<<i<<"=TLE;\nfi\n";
        cout<<"if @score"<<i<<">=2000; then\n@status"<<i<<"=AC;\nfi\n";
        cout<<"if @score"<<i<<">=3000; then\n@status"<<i<<"=1;\nfi\n";
        cout<<"if @score"<<i<<">=4000; then\n@status"<<i<<"=WA;\nfi\n";
    }
    cout<<"fi";
    return 0;
}

:::info[生成的计分脚本(共 594 行)]

@final_time=@score1%1000;
if @score1>=10000; then
@total_score=2147483647;
@final_status=AC;
else
if @score1>=1000; then
@status1=TLE;
fi
if @score1>=2000; then
@status1=AC;
fi
if @score1>=3000; then
@status1=1;
fi
if @score1>=4000; then
@status1=WA;
fi
if @score2>=1000; then
@status2=TLE;
fi
if @score2>=2000; then
@status2=AC;
fi
if @score2>=3000; then
@status2=1;
fi
if @score2>=4000; then
@status2=WA;
fi
if @score3>=1000; then
@status3=TLE;
fi
if @score3>=2000; then
@status3=AC;
fi
if @score3>=3000; then
@status3=1;
fi
if @score3>=4000; then
@status3=WA;
fi
if @score4>=1000; then
@status4=TLE;
fi
if @score4>=2000; then
@status4=AC;
fi
if @score4>=3000; then
@status4=1;
fi
if @score4>=4000; then
@status4=WA;
fi
if @score5>=1000; then
@status5=TLE;
fi
if @score5>=2000; then
@status5=AC;
fi
if @score5>=3000; then
@status5=1;
fi
if @score5>=4000; then
@status5=WA;
fi
if @score6>=1000; then
@status6=TLE;
fi
if @score6>=2000; then
@status6=AC;
fi
if @score6>=3000; then
@status6=1;
fi
if @score6>=4000; then
@status6=WA;
fi
if @score7>=1000; then
@status7=TLE;
fi
if @score7>=2000; then
@status7=AC;
fi
if @score7>=3000; then
@status7=1;
fi
if @score7>=4000; then
@status7=WA;
fi
if @score8>=1000; then
@status8=TLE;
fi
if @score8>=2000; then
@status8=AC;
fi
if @score8>=3000; then
@status8=1;
fi
if @score8>=4000; then
@status8=WA;
fi
if @score9>=1000; then
@status9=TLE;
fi
if @score9>=2000; then
@status9=AC;
fi
if @score9>=3000; then
@status9=1;
fi
if @score9>=4000; then
@status9=WA;
fi
if @score10>=1000; then
@status10=TLE;
fi
if @score10>=2000; then
@status10=AC;
fi
if @score10>=3000; then
@status10=1;
fi
if @score10>=4000; then
@status10=WA;
fi
if @score11>=1000; then
@status11=TLE;
fi
if @score11>=2000; then
@status11=AC;
fi
if @score11>=3000; then
@status11=1;
fi
if @score11>=4000; then
@status11=WA;
fi
if @score12>=1000; then
@status12=TLE;
fi
if @score12>=2000; then
@status12=AC;
fi
if @score12>=3000; then
@status12=1;
fi
if @score12>=4000; then
@status12=WA;
fi
if @score13>=1000; then
@status13=TLE;
fi
if @score13>=2000; then
@status13=AC;
fi
if @score13>=3000; then
@status13=1;
fi
if @score13>=4000; then
@status13=WA;
fi
if @score14>=1000; then
@status14=TLE;
fi
if @score14>=2000; then
@status14=AC;
fi
if @score14>=3000; then
@status14=1;
fi
if @score14>=4000; then
@status14=WA;
fi
if @score15>=1000; then
@status15=TLE;
fi
if @score15>=2000; then
@status15=AC;
fi
if @score15>=3000; then
@status15=1;
fi
if @score15>=4000; then
@status15=WA;
fi
if @score16>=1000; then
@status16=TLE;
fi
if @score16>=2000; then
@status16=AC;
fi
if @score16>=3000; then
@status16=1;
fi
if @score16>=4000; then
@status16=WA;
fi
if @score17>=1000; then
@status17=TLE;
fi
if @score17>=2000; then
@status17=AC;
fi
if @score17>=3000; then
@status17=1;
fi
if @score17>=4000; then
@status17=WA;
fi
if @score18>=1000; then
@status18=TLE;
fi
if @score18>=2000; then
@status18=AC;
fi
if @score18>=3000; then
@status18=1;
fi
if @score18>=4000; then
@status18=WA;
fi
if @score19>=1000; then
@status19=TLE;
fi
if @score19>=2000; then
@status19=AC;
fi
if @score19>=3000; then
@status19=1;
fi
if @score19>=4000; then
@status19=WA;
fi
if @score20>=1000; then
@status20=TLE;
fi
if @score20>=2000; then
@status20=AC;
fi
if @score20>=3000; then
@status20=1;
fi
if @score20>=4000; then
@status20=WA;
fi
if @score21>=1000; then
@status21=TLE;
fi
if @score21>=2000; then
@status21=AC;
fi
if @score21>=3000; then
@status21=1;
fi
if @score21>=4000; then
@status21=WA;
fi
if @score22>=1000; then
@status22=TLE;
fi
if @score22>=2000; then
@status22=AC;
fi
if @score22>=3000; then
@status22=1;
fi
if @score22>=4000; then
@status22=WA;
fi
if @score23>=1000; then
@status23=TLE;
fi
if @score23>=2000; then
@status23=AC;
fi
if @score23>=3000; then
@status23=1;
fi
if @score23>=4000; then
@status23=WA;
fi
if @score24>=1000; then
@status24=TLE;
fi
if @score24>=2000; then
@status24=AC;
fi
if @score24>=3000; then
@status24=1;
fi
if @score24>=4000; then
@status24=WA;
fi
if @score25>=1000; then
@status25=TLE;
fi
if @score25>=2000; then
@status25=AC;
fi
if @score25>=3000; then
@status25=1;
fi
if @score25>=4000; then
@status25=WA;
fi
if @score26>=1000; then
@status26=TLE;
fi
if @score26>=2000; then
@status26=AC;
fi
if @score26>=3000; then
@status26=1;
fi
if @score26>=4000; then
@status26=WA;
fi
if @score27>=1000; then
@status27=TLE;
fi
if @score27>=2000; then
@status27=AC;
fi
if @score27>=3000; then
@status27=1;
fi
if @score27>=4000; then
@status27=WA;
fi
if @score28>=1000; then
@status28=TLE;
fi
if @score28>=2000; then
@status28=AC;
fi
if @score28>=3000; then
@status28=1;
fi
if @score28>=4000; then
@status28=WA;
fi
if @score29>=1000; then
@status29=TLE;
fi
if @score29>=2000; then
@status29=AC;
fi
if @score29>=3000; then
@status29=1;
fi
if @score29>=4000; then
@status29=WA;
fi
if @score30>=1000; then
@status30=TLE;
fi
if @score30>=2000; then
@status30=AC;
fi
if @score30>=3000; then
@status30=1;
fi
if @score30>=4000; then
@status30=WA;
fi
if @score31>=1000; then
@status31=TLE;
fi
if @score31>=2000; then
@status31=AC;
fi
if @score31>=3000; then
@status31=1;
fi
if @score31>=4000; then
@status31=WA;
fi
if @score32>=1000; then
@status32=TLE;
fi
if @score32>=2000; then
@status32=AC;
fi
if @score32>=3000; then
@status32=1;
fi
if @score32>=4000; then
@status32=WA;
fi
if @score33>=1000; then
@status33=TLE;
fi
if @score33>=2000; then
@status33=AC;
fi
if @score33>=3000; then
@status33=1;
fi
if @score33>=4000; then
@status33=WA;
fi
if @score34>=1000; then
@status34=TLE;
fi
if @score34>=2000; then
@status34=AC;
fi
if @score34>=3000; then
@status34=1;
fi
if @score34>=4000; then
@status34=WA;
fi
if @score35>=1000; then
@status35=TLE;
fi
if @score35>=2000; then
@status35=AC;
fi
if @score35>=3000; then
@status35=1;
fi
if @score35>=4000; then
@status35=WA;
fi
if @score36>=1000; then
@status36=TLE;
fi
if @score36>=2000; then
@status36=AC;
fi
if @score36>=3000; then
@status36=1;
fi
if @score36>=4000; then
@status36=WA;
fi
if @score37>=1000; then
@status37=TLE;
fi
if @score37>=2000; then
@status37=AC;
fi
if @score37>=3000; then
@status37=1;
fi
if @score37>=4000; then
@status37=WA;
fi
if @score38>=1000; then
@status38=TLE;
fi
if @score38>=2000; then
@status38=AC;
fi
if @score38>=3000; then
@status38=1;
fi
if @score38>=4000; then
@status38=WA;
fi
if @score39>=1000; then
@status39=TLE;
fi
if @score39>=2000; then
@status39=AC;
fi
if @score39>=3000; then
@status39=1;
fi
if @score39>=4000; then
@status39=WA;
fi
if @score40>=1000; then
@status40=TLE;
fi
if @score40>=2000; then
@status40=AC;
fi
if @score40>=3000; then
@status40=1;
fi
if @score40>=4000; then
@status40=WA;
fi
if @score41>=1000; then
@status41=TLE;
fi
if @score41>=2000; then
@status41=AC;
fi
if @score41>=3000; then
@status41=1;
fi
if @score41>=4000; then
@status41=WA;
fi
if @score42>=1000; then
@status42=TLE;
fi
if @score42>=2000; then
@status42=AC;
fi
if @score42>=3000; then
@status42=1;
fi
if @score42>=4000; then
@status42=WA;
fi
if @score43>=1000; then
@status43=TLE;
fi
if @score43>=2000; then
@status43=AC;
fi
if @score43>=3000; then
@status43=1;
fi
if @score43>=4000; then
@status43=WA;
fi
if @score44>=1000; then
@status44=TLE;
fi
if @score44>=2000; then
@status44=AC;
fi
if @score44>=3000; then
@status44=1;
fi
if @score44>=4000; then
@status44=WA;
fi
if @score45>=1000; then
@status45=TLE;
fi
if @score45>=2000; then
@status45=AC;
fi
if @score45>=3000; then
@status45=1;
fi
if @score45>=4000; then
@status45=WA;
fi
if @score46>=1000; then
@status46=TLE;
fi
if @score46>=2000; then
@status46=AC;
fi
if @score46>=3000; then
@status46=1;
fi
if @score46>=4000; then
@status46=WA;
fi
if @score47>=1000; then
@status47=TLE;
fi
if @score47>=2000; then
@status47=AC;
fi
if @score47>=3000; then
@status47=1;
fi
if @score47>=4000; then
@status47=WA;
fi
if @score48>=1000; then
@status48=TLE;
fi
if @score48>=2000; then
@status48=AC;
fi
if @score48>=3000; then
@status48=1;
fi
if @score48>=4000; then
@status48=WA;
fi
if @score49>=1000; then
@status49=TLE;
fi
if @score49>=2000; then
@status49=AC;
fi
if @score49>=3000; then
@status49=1;
fi
if @score49>=4000; then
@status49=WA;
fi
fi

::: :::: 我们一个个说怎么配。

1. checker

checker 是最关键的部分。在测试点地图题中,checker 的作用不再是判题,而是给自定义积分脚本传参。 传参的方式是返回分数。

首先,你的 checker 需要知道它在处理哪个测试点。显然的一个方法是在答案文件中放置测试点坐标,这样就能让 checker 读到,做题人读不到了。这样就交给 datagen.cpp 了。

其次就是返回的具体分数。为了方便,使用 config.yml 将每个测试点满分置为 1 分。 我的代码中返回的分数是:

其中 c 为你的有效操作数(撞墙也算)。(现在应该能明白我为什么限制 999 步了,因为测试点判断会状态有冲突。)

此外,还需要特判一些特殊的状态,例如通关,步数超限。我在通关时返回 $10000+c$ 分,步数超限时返回 $0$ 分。在计分脚本处也要相应特判。 现在的问题只有模拟走迷宫的过程了,对于学 OI 的我们这是容易的。 :::info[说一下代码的细节]{open} - `if(!ouf.seekEof())` 的判断是为了输出为空串时 checker 能够正常运作。 - `px,py` 两个变量是为了控制显示的具体 $7 \times 7$ 区域而维护的,它们的坐标是 $7 \times 7$ 的中心。它们的取值逻辑是:让玩家尽量在中心,但若显示区域溢出地图则相应平移。 ::: ## 2. data 这个就比较简单了,在答案文件中输出测试点对应的坐标即可。 以及一个细节:输入的空文件也要造。 ## 3. config 也没啥好说的。令每个测试点 $1$ 分即可,其它都不用动。 ## 4. 计分脚本 这个也很重要。 首先你需要做一下 checker 部分提到的特判,套一层 if 即可。 :::info[怎么没写步数爆炸的特判?]{open} 其实是我一开始忘写了,然后发现竟然可以正常工作,所以就这样了。 ::: 之后就是对每个测试点的判断。 :::info[if 语句的推荐写法]{open} 由于 else if 在自定义计分脚本需要嵌套 if,比较麻烦,因此我使用了多次 if 的方法,满足后面的条件就覆盖掉前面的。当然也可以每次判断一个分数区间。 ::: 注意:虽然帮助中心里说设置测试点状态为未定义值会 UKE,但是不能随便放一个就用了,**必须是一个数值**。用 `1` 就行。 参数从分数内读取是容易的。如果需要改变测试点时间 / 空间可以直接用分数进行运算设置值,单位分别是 ms / KB。 最后一些细节:`@final_status` 如果未设置会是 `UNAC`,其它规定设置的常量未设置会是 $0$。 ## 5. 总结 虽然我说起来很简单,但是这种测试点地图题是很难配的,特别是比较复杂的一些题,checker 和计分脚本的压力会很大,计分脚本还限长,有时还要压缩长度。不过配出来还是很有趣的!