推一下蒟蒻 $\color{salmon}{blog}$ 。
_写在最前:本文的代码和官方题解没什么区别,只是加一点个人的解释,权且做一个注脚_
为数不多的炉石背景题。
难不见得难,就是装腔作势,借以吓人罢了。
广义上看,从题面、赛时过题人数、代码谢特的程度来说,这确实是一道大模拟。
而精确地讲,这是一道伪装成大模拟的大暴搜,思想可能和【NOIP2015 斗地主】类似。
即:『用搜索来决策当前这一步应该干什么,会带来什么后果。』
定义一个 $dfs$ 函数,返回型 $double$ ,表示在 这个局面下操作者的胜率 。
显然这个可以 记忆化 。
先列好我们能在一局中干什么。
- 用随从攻击
- 开技能(嘬一口)
- 打牌(开biubiubiu)
- 结束回合。
这些操作有优先级的限制吗?
显然是没有的,我们可以在任何时候嘬一口,也可以在任何时候结束回合。
先看随从攻击这一环节。
一个随从存在两个攻击状态,可以攻击(能动)和不可攻击(不能动)。
同时,他还有两种血量的状态(如果他没有GG的话),一血和两血。
这使我们想到,要保存当前一个玩家的随从场面,需要开四个参数,将血量状态(1/2)和攻击状态(1/0)一一配对,参数的值表示 处于该状态的随从个数 ,定为 $x_{11},x_{10},x_{21},x_{20}$ 。
对于当时对面的场面,我们不关心他们是否能动,就开 $y_1,y_2$ 分别代表 某血量对应的个数 即可。
因为 看了题解 要尽量减小 $dfs$ 函数的谢特程度,我们在 函数传参 时,把一血怪的两种参数压进一个两位数里,十位上是 能动 的一血怪数,个位上是 不能动 的一血怪数,等到函数内部要用值的时候再用 $\%10$ 或 $\div10$ 取出来。二血怪同理。(这里笔者换结构体写了一下,发现怎么调也调不出,最后还是向压缩屈服了)
传参:
1 | double dfs(int HPx, int HPy, int MINx1, int MINx2, int MINy, int cnt, int cardx, int cardy, int biu) |
处理参数:
1 | /*MINx1,MINx2,MINy 都是从函数传进来经过压缩的参数*/ |
一个 $k$ 点血且能动的怪要么去交换场面,与对面的随从同归于尽,即 $x_{k1}—,y_?—$;要么直接打脸,攻击对方减 $3$ 点血,即 $x_{k1}—,x_{k0}++,HP_y-=3$ 。
随从攻击:
1 | double prob;//该场面我方胜率 |
其次是开技能。
嘬一口需要满足三个条件:有技能次数,自己血量高于两血,手牌小于 $3$ 张(一血不慌,二血健康,三血抽口也不伤)。
我们开个 $if$ 判一下就是了。
嘬一口:
1 | /*上接随从攻击*/ |
终于来到了本题最复杂最宏伟的结构——biubiubiu 了。
一个 biubiubiu 分为三次伤害,两次伤害之间 有结算 。换句话说,不会有伤害落在死人身上。
这启示我们把一个 biubiubiu 拆成三个连续的 单点伤害 来看,只不过在三个伤害之间 玩家不能操作 罢了。
我们令参数 $biu$ 代表当前场面是在第几次 biubiubiu 之后的场面,若为 $1/2$ ,则说明这是一个在 biubiubiu 期间的局面,既然在 biubiubiu 期间,那我们唯一能做的就是枚举下一发落到哪,对所有情况取个平均数以体现伪随机即可。
枚举 biubiubiu:
1 | double dv = 1.0 / tot;//求平均数用的分母,tot是总目标数,赋值在“参数处理”部分 |
而在非 biubiubiu 期间,我们要开一次 biubiubiu 的条件又是什么呢?手上有牌,剩余出牌次数>0,我方场上随从<=3 。
开一次 biubiubiu :
1 | /*上接使用技能*/ |
最后就是我们的结束回合操作了。
结束这个回合,我们会得到【1.0-对手下回合胜率】的胜率。
我们如何表示下一个回合中对手的胜率?是不是 以对手为主视角 ,做一次 $dfs$ ,参数如下:
1 | dfs(HPy, HPx, y1 * 10, y2 * 10, (x21 + x20) * 10 + (x11 + x10), O, min(cardy + 1, 3), cardx, 3) |
我们发现这里的 $biu$ 参数居然是 $3$ ,这又代表什么。
其实是高明的THU爷把 $biu$ 同时还当作了一个标记,因为题目里说一个回合不能啥事不干地 空过 ,所以当 $biu=3$ 其实是 还没有执行过操作 的情况。
所以在 $biu=3$ 时,我们将 $prob$ 初始化为 0 ,否则则至少有 【1.0-对手下回合胜率】作为保底。
初始化 prob :
1 | /*上接“是否在biubiubiu期间”的判断*/ |
这样我们就有了获取某场面胜率的基本方式,现在我们可以整合起来看看。
1 | double dfs(int HPx, int HPy, int MINx1, int MINx2, int MINy, int cnt, int cardx, int cardy, int biu) |
然后主函数有手就行(((
这里再点两个细节:
- 有一个有效而可行的剪枝是,在不是 biubiubiu 期间,且当前手上随从 全部打脸 就能赢的时候,直接返回 1.0 。(亲测每个点可以平均-3s)
1 | if (HPy <= (x11 + x21) * 3) |
- 记得把 y1
define
掉。/kk
想来想去还是放送一下主函数比较好:
1 |
|