#P12705. [致理杯 2025 div 2] plane [本题没法测评]
[致理杯 2025 div 2] plane [本题没法测评]
炸飞机(plane)
题目描述
炸飞机是一款双人对战游戏。游戏双方初始各有一张 的格子纸。格子左上角为格子 ,左下角为坐标 ,右上角为坐标 。飞机是一个可旋转的形状固定的四连通连通块,其中有一个特殊的格子,称之为机头,其余格子称为机身。游戏开始前,会通过掷硬币的方式决定先后手。接着,每个玩家需要在自己的格子纸上不重叠地画 架飞机。飞机有多种型号,各型号飞机的形状见文末提示处。之后,两名选手将轮流进行以下操作:
选择以下两个操作之一进行:
-
打击操作:询问对方的格子纸中的方格 上是机头,还是机身,还是都不是。如果是机头,将击毁该机头所在的飞机。之后,对方会得知你所询问的 。
-
移动操作:将自己的第 架飞机向某个方向平移一格。你需要保证此时你的这架飞机未被击毁,且移动过后仍与其他飞机并不重叠(包括已经被击毁的飞机)。之后,对手将检测到气流流动的改变,得知你移动的方向,但是不知道你移动的是哪一架飞机。
最先击毁对方三架飞机的玩家为胜者。每名玩家最多各自进行 次操作。如果 次操作后仍未分出胜负,则后手获得胜利。
你需要设计一个程序,来使自己尽可能地成为游戏的胜者。
实现方式
在一次程序运行的过程中,总共会进行 次游戏。
本题采用函数交互的形式。
下文给出 C++ 的实现方法。其他语言的实现方法与 C++ 类似,详情参考下发文件中的示例文件与示例代码。
下文中认为飞机标号和型号均从 开始标号。
你不需要,也不应该实现主函数。
你应确保提交的程序包含头文件 game.h,可在程序开头加入以下代码实现:
#include "game.h"
选手可以调用以下三个函数:
void Draw(int kind,int direction,int x,int y)
你只能在 Init
函数中调用这个函数。你可以通过调用这个函数来放置一架飞机。 为飞机的型号, 只能是 的一个,分别表示飞机不进行旋转,顺时针旋转 度,顺时针旋转 度,顺时针旋转 度。 分别表示飞机的第一维坐标和第二维坐标。你需要保证飞机之间不会产生重叠,且飞机没有出界。
int Hit(int x,int y)
你只能在 Operate_self
函数中调用这个函数。你可以通过调用这个函数来进行一次打击操作。 分别表示选手希望询问的第一维坐标与第二维坐标。该函数会返回 中的一个。如果返回 ,则对手的方格 上为空白;如果返回 ,则对手的方格 上为机身。如果返回 ,则对手的方格 上为机头。
void Move(int id,int direction)
你只能在 Operate_self
函数中调用这个函数。你可以通过调用这个函数来进行一次移动操作。 表示移动的飞机的编号。 应为 中的一个,分别表示你将飞机向上、右、下、左移动一格。对于机头原本在 的飞机,向上、右、下、左移动后机头的位置分别为:。你需要保证你移动的飞机未被击中机头,且移动后飞机不会产生重叠与出界。
选手需要实现以下三个函数:
void Init(int last,int id,vector<int> type,bool b);
-
表示上一把游戏的胜负情况。如果上一把获胜,。如果上一把失败,。如果这是第一把游戏,。
-
表示子任务的编号
-
中一共有三个元素。 在二进制下的从低到高第 位(从 开始标号)如果为 ,则第 架飞机可选用型号 ,否则第 架飞机不能选用型号 。
-
为 表示你是先手,否则你是后手。
该函数会在每场游戏开始前被调用一次。你必须在 Init
中调用恰好三次 Draw
函数,第 次调用表示放置编号为 的飞机。
void Operate_self();
该函数会在轮到你操作时被调用一次。你必须在 Operate_self
中调用一次 Hit
或 Move
函数。你不能在一次 Operate_self
的运行过程中同时调用 Hit
和 Move
。在一次 Operate_self
的运行过程中,Hit
和 Move
都至多只能调用一次。
void Operate_opponent(bool kind,int x,int y);
该函数会在对手操作后被调用一次。如果 为 ,则对手刚刚进行了一次打击操作,且询问了位置 。否则对手刚刚进行了一次移动操作,当 时分别意味着对手将某架飞机向上移动一格,向右移动一格,向下移动一格,向左移动一格。对于机头原本在 的飞机,向上、右、下、左移动后机头的位置分别为:。
如果某人在某个测试点运行过程中出现了 re,tle,mle 或非法的函数调用,则该测试点所有数据均判定为对手胜利。如果两人在某个测试点运行过程中均出现 re,tle,mle 或非法的函数调用,则无法保证该测试点最终结果。
测试点
下文中 均以二进制形式给出。
测试点编号 | 分值 | |||||
---|---|---|---|---|---|---|
为降低一部分运气对结果的影响,保证在每个测试点的 组数据中对于每位玩家均有 次先手, 次后手。
保证交互库的运行时间不会超过 1s。
测试方式
下文给出 C++ 的实现方法。其他语言的实现方法与 C++ 类似,详情参考下发文件中的示例文件与示例代码。
你需要打开 grader.cpp 的源代码。用你的代码替换 namespace player
中的注释部分,并用对手的代码替换 namespace opponent
中的注释部分。将 grader.cpp 编译得到可执行文件。
在可执行文件中,你需要输入一行四个整数 。之后,将会进行 局游戏。之后,将根据双方程序运行结果,给出对应的反馈。特别的,反馈中的 player0 指代你的代码,player1 指代对手的代码。
下发文件中的 sample.cpp 给出了一种满足子任务 要求的实现方式。如果你希望你的代码和对手的代码均为 sample.cpp,则你按上述方法修改 grader.cpp 得到的新程序应类似于 grader_sample.cpp。你可以直接编译运行 grader_sample.cpp,按上述格式输入得到两个 sample.cpp 的对战反馈。
评分方式
对于接下来的内容,选手不必了解所有细节,建议只阅读加粗的部分,但如果有兴趣还是可以详细了解。
本题不采取即时反馈赛制。 在比赛正式开始后,每经过 分钟,评测机将执行一次批量评测。具体而言,对于每位选手,评测机将会取其最后一次提交且编译成功的代码作为有效提交。在每次批量评测前 5 分钟,将会有通知提醒选手及时提交参与本次批量评测的代码,请选手务必注意提交的时机。
本题采用 Elo rating 对选手程序进行排名与计算分数。每个测试点都有独立的 rating 和排名,选手可以针对某几个测试点特别优化自己的程序以获得较高的分数。
在一次批量评测中,对于每个测试点,每份代码的 初始 rating 为 ,然后评测机会组织若干轮匹配赛( 轮左右),每轮匹配赛中将采取如下方式确定每位选手的对手:
- 所有选手的顺序将被完全打乱。
- 依次遍历每位尚未匹配的选手,尝试采用如下机制寻找对手:
- 初始匹配窗口为 。
- 每次查询尚未匹配的选手中,rating 与当前选手差距不大于匹配窗口的选手。如果有多个选手,随机抽取一个作为当前选手的对手,当前选手匹配完毕。
- 如果不存在这样的选手,将匹配窗口扩大 并转至步骤 ii,直至匹配窗口到达上限 。
- 如果最终匹配失败,当前选手记为轮空,不参与本轮匹配赛。
在一次对战中,设两名选手分别为 和 ,设 当前的 rating 分别为 与 。定义 $E_A = \frac{1}{1 + 10^{(R_B - R_A)/400}},E_B=\frac{1}{1 + 10^{(R_A - R_B)/400}}$。令 和 分别为 与 在此次对战中该测试点的胜率。对战后,双方在该测试点 rating 将被更新为 ,计算方式如下:
$$R_A' = R_A + K (S_A - E_A) \\ R_B' = R_B + K (S_B - E_B) $$其中 , 为当前是第几轮匹配赛, 为匹配赛的总轮数。
所有匹配赛轮次结束后,选手在每个测试点的 rating 也即确定。接下来进入分数统计环节。我们采用赋分制将选手的 rating 转化为分数,其机制如下:
对于每个测试点,我们会先按 rating 从高到低对选手排序,将选手分为 A,B,C,D,E 五档。
等级 | 排名范围 | 分数比例范围 |
---|---|---|
A | ||
B | ||
C | ||
D | ||
E |
每位选手在该测试点的分数计算公式为:
$$\text{score}=\mathrm{fullScore} \times (\mathrm{minRatio} + (\mathrm{maxRatio}-\mathrm{minRatio}) \times \frac{\mathrm{rating}-\mathrm{minRating}}{\mathrm{maxRating}-\mathrm{minRating}}) $$其中, 为该测试点的分值, 为当前选手在本测试点的 rating, 为选手所处档次的分数比例的下限与上限, 为选手所处档次的所有选手中 rating 的最小值与最大值。
例:某位选手在测试点 1(满分 分)的 rating 为 ,处于 C 档,该档次的选手中 rating 最高 ,最低 ,根据上述公式计算出该选手在测试点 1 的得分为 分。
在本次批量评测中,选手的最终得分为各测试点得分之和。选手可以通过分数的详情页面了解各测试点的得分、rating 及排名。
比赛结束后,为了更准确地确定选手的表现与排名,我们可能会进行额外的若干轮匹配赛。
在一次批量评测开始时,评测机会自动将上一次的提交复制一份,用于下一次批量评测。此次批量评测结束后,选手可以在原来的提交中看到此次批量评测的得分。
选手在该题的最终得分为最后一次批量评测后的选手得分(而非历史最高分)。新的提交会覆盖旧的提交,建议选手提交代码前确定新代码相对于旧代码的优劣,如采用自我对打等方式。
提示
飞机的各种机型在不进行旋转的模样如下:
机型 :
机型 :
机型 :
机型 :
机型 :
机型 :