抽卡与掉落(2 道概率题)

本文最后更新于:2022年8月10日 早上

之前接触数值的时候遇到了一些有趣的概率题目,涉及氪金游戏的抽卡,物品掉落等等。一直留着代码却迟迟没有记录下来并给出解释,也不知道把代码继续存到那里。于是打算整理成文章,一则可以保留代码,二则可以保留思路甚至与他人交流。

我的解法都是基于代码的硬算,使用了期望的基本定义:

$$\mathbb{E}(X)=\sum_i^N x_i*p_i$$

即事件*概率的累加。

题目 1:伪随机思路的抽卡与保底

自从抽卡游戏需要公布概率以来,如何设计抽卡的概率也成为了一门需要不低数学门槛的学问(其实不需要公布概率的时候也是很深的学问)。虽然厂商公布了一个概率,但这个概率没有标注的那么简单,往往是引入保底的综合概率,每一次抽卡实际的概率与公布的概率并不完全一致。

关于抽卡,厂商们多多少少会引入一些「伪随机」概念。这里的「伪随机」并不是指计算机程序原理层面的「伪随机」,而是一种应用层面的随机性。

举个例子,某抽卡过程有 20% 的概率的抽中,那么理论抽中的期望次数是 5。如果使用「真随机」,即每一次都有 20% 的概率抽中,事件独立,那么 5 次没抽中的概率高达$0.8^5=0.32768$,也就是有差不多 1/3 的人不能在 5 次以内抽中;而另一方面,抽中 2 次的概率约为 20%,3 次 5%,而一次约为 40%,运气比较好的玩家约占 1/4。这与玩家的期待是不一致的,欧皇过于欧,非酋也过于非

所以,为了让抽卡结果更加符合玩家的心理,需要研究出一种新的抽卡概率模型,使得欧皇的概率降低,非酋的概率也降低,但整体上抽卡的概率保持不变。这一「伪随机」理念同样适用于「暴击」(或者说是来源),有一定概率暴击和抽卡是完全一致的。

经典的例子的是《WAR3》中的暴击,一个暴击概率 20% 的英雄,并不是刀刀 20% 的概率暴击,而是首刀 5.57% 概率暴击,如果不暴击,则之后暴击概率变成 2 倍、3 倍……直至暴击则重置概率,从首刀的 5.57% 开始算。如此下来,综合暴击概率仍为 20%,却有效减少了刀刀暴击或者迟迟不暴击的尴尬局面。

以上只是一个比较简单的例子,实际上伪随机的算法处理起来非常多样化,比如可以按照某个函数递增概率,或者干脆设定好每一次的概率。不过从最终综合概率结果拟合概率变化函数过于复杂(我不会),我只会用已知概率函数正推综合概率。

比如《明日方舟》中标准池抽到六星的概率规则:

《明日方舟》抽卡概率规则

简单写了一段代码计算抽中六星的期望抽卡次数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import math

c = 0.02
add = 0.02
n = 50
currentp = 0
successp = 0
expectation = 0
maxN = n + math.ceil((1-c) / add)
for i in range(maxN):
if i < n:
currentp = (1 - successp) * c
elif i < maxN - 1:
currentp = (1 - successp) * (c + (i-n+1) * add)
else:
currentp = 1 - sucessp
successp += currentp
expectation += currentp * (i + 1)

print(expectation)

代码的基本思路就是计算第一次抽中的概率、第二次抽中的概率……直至第 99 次抽中的概率(100%),然后用期望的基本公式求和。

稍微解释一下代码:c 是初始概率;add 是 50 抽之后的递增概率;n 表示之后概率递增;maxN 表示此时抽中概率已为 100%;循环结构内 i+1 表示抽卡次数(循环从 i=0 开始);successp 代表 i 次抽卡已抽中的概率,经过一次抽卡后累加当前抽中的概率;currentp 代表本次抽中的概率,即累计未抽中 (1-successp)*当前概率 (分 50 抽前后两种情况);期望就是当前抽中概率(currentp)*当前抽卡次数(i+1)的累加。

最后运行下来,期望约为 34.59,也就是平均 35 抽可以得到一个六星干员,好像与自己的抽卡体验挺符合的。什么你说双角色池歪了,那没事了。

整个代码框架可以微调一些参数,或通过设置各次抽卡的概率,套用到其他「伪随机」事件中。

题目 2:怪物掉落物品凑成整套装备

首先是一个较简单的问题:打一次怪物随机掉落五种装备中的一种,问收集齐五种装备打怪物次数的期望是多少?

这题可以一步一步推:获取第一个装备的期望是 1;第二个装备不重复的概率为 4/5,所以获得 2 个不重复装备的期望为 1+5/4;依此类推,可得收集齐的期望为 1+5/4+5/3+5/2+5/1=11.42。

后来又遇到了一个稍微棘手一点的问题升级版:打一次怪物随机掉落五种装备中的三个(不重复),问收集齐五种装备打怪物次数的期望是多少?

这个改进版的问题一开始着实困扰到我,所以一怒之下打算用暴力模拟的方式解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import random

a = [1,2,3,4,5]
sum = 0
for j in range(1,10000):
b = []
i = 1
while(i < 100):
b += random.sample(a,3)
if set(a) == set(b):
sum += i
break
i += 1
exp = sum / j
print(exp)

非常简单的暴力模拟,进行 10000 次集齐装备试验,记录每次试验中集齐 5 个装备的打怪次数然后求平均值,模拟出来大概 3.2 附近。

后来在进一步的思考下,决定使用抽卡的期望算法来准确计算,先上代码:

1
2
3
4
5
6
7
8
9
10
exp = 0
for i in range(2,40):
j = i
exp1 = pow(0.1,i-2)*0.3*i
exp += exp1
while j > 2:
exp2 = pow(0.1,j-3)*0.6*pow(0.4,i-j)*0.6*i
j -= 1
exp += exp2
print(exp)

解题思路是第一次打肯定掉落 3 个装备,不会集齐,所以从第二次打怪开始计算概率和期望。

一些基础概率,使用排列组合知识易得:

1
2
3
4
5
6
7
8
# 3 #已经掉落三个的前提下
# p123 = 0.1 #继续掉落前三个
# p4or5 = 0.6 #掉落第四个或第五个
# p4and5 = 0.3 #同时掉落第四个和第五个

# 4 #已经掉落四个的前提下
# p5 = 0.6 #掉落第五个
# p1234 = 0.4 #掉落前四个

这里分两种情况讨论:

  1. 第四个和第五个在某一次打怪中一起掉落(exp1)。
  2. 先掉落第四个,再掉落第五个(exp2)。

第一种情况需要第 i 次前都未掉落 4 或 5,然后第 i 次同时掉落 4 和 5,此事件的概率为 pow(0.1,i-2)*0.3

第二种情况需要考虑是第几次掉落第四个装备,可能是第 2 指 i-1 次,同时再嵌套一次循环遍历所有可能。然后考虑单次循环内前三个掉落的前提下继续掉落前三个的情况需要持续几次,以及前四个掉落的前提下继续掉落前四个的情况需要持续几次,此事件的概率为 pow(0.1,j-3)*0.6*pow(0.4,i-j)*0.6

需要注意的是,理论上足够非的话永远也凑不齐 5 件套,所以程序仅模拟一定的打怪次数。实际上,当放开次数到 40 次时,期望已经收敛至 3.222222222222156,也就是尽管存在后续凑齐套装的概率,也基本上对期望没什么影响了。

通过这种方法得到的期望和暴力模拟的期望基本上一致,预计此方法正确。

小结

以上就是本文关于两道有趣概率题的解法,方法或结果可能有误,或者存在其他更巧妙的解法,欢迎讨论。


抽卡与掉落(2 道概率题)
https://skeathytomas.github.io/post/抽卡与掉落(2-道概率题)/
作者
Skeathy
发布于
2022年4月23日
许可协议