来自魔法纪录中文Wiki
跳转至: 导航 搜索

连续抽卡出货的概率怎么算?抽数和出货个数之间的规律是怎样的?这篇文章就是用中学阶段的知识(顶多是数学竞赛初赛难度)来解答这些问题,很简单的哦~

和很多人编程模拟抽卡然后统计结果不一样,以下全部都是精确计算,而非模拟抽卡。

哈吉马路哟~简单易懂的抽卡数学~

只用单抽券的情形

定义

这里先忽略60%概率UP,把抽出四星就叫做“出货”。

同时也忽略十连抽卡的特殊概率,仅使用出货概率的单抽券。

用随机变量表示单抽次后得到的四星魔法少女的个数。

“单抽次出货个”这一事件便记作,该事件发生的概率记作

那么当不变时,随机变量的数学期望就是

例如:单抽1次出货1个的概率,出货0个的概率,因此单抽1次的出货个数的数学期望

简单易懂的切入角度

考虑最简单的情形,

由于两次单抽是独立随机事件事件,假如用分别代表第一抽出货和不出货的概率、用代表第二抽出货和不出货的概率的话,那么

这就是数列第二项与第一项之间的递推关系。

同理,数列第三项与前两项的递推关系就是

可以注意到,上式最后一项代表的是第三抽出了货的情形,倒数第二项代表的是第三抽没出货、第二抽出了货的情形,剩下的那项代表的是第三抽和第二抽都没出货的情形。

简单易懂的推广

因此,推广到101项的时候,可以同理写出

不过这里有一个小小的问题:连续99抽不出货之后,第100抽不出货也要出货。

上式的前面的项都包含有某个,对应了至少有一次出货的情形,唯独最后一项代表第2抽到第101抽均不出货,因此该项应修正为

然后用来重写上式,就得到

将上式的替换成便可得到数列的阶线性递推关系:

不难发现上式的常数项,因此可以写成

求解数列通项公式

首先来去掉常数项:定义一个新的数列,其中是待定常系数,那么递推关系就成了

令递推关系中的常数项等于零,即

因此要消去常数项,只需令

(其中使用了关系式来化简)

然后我们写出齐次递推数列的特征方程:

点击此处尝试求解该方程,并发现方程的解近似于均匀分布在复平面单位圆上。

点击此处严格求该方程当时的精确解,得到四个解。

于是猜想特征方程的个复根应该就是),代入特征方程不难验证其成立。

有了特征根,可以直接看出)是种符合数列递推关系的解。

它们线性组合成数列递推关系的通解

其中个待定常系数。

另一方面,数列的前100项是已知的:当即还没到保底时,每一抽的数学期望都是并且互相独立,因此

用这条等式求解个待定系数,可以算得

(其中使用了关系式来化简)

结论

单抽次后得到的四星魔法少女个数的数学期望是

代入的表达式则是:

代入得到数值表达式:

假如要求的是pickup四星魔法少女的个数的数学期望,则需往上式再乘以

注意当超过100之后,上式第三项将迅速下降到不足0.01的程度,整体走势近似于线性增长:

只用十连券的情形

定义

先忽略60%概率UP,不过这次只使用十连券,当中包含一次出货概率的单抽和九次出货概率的单抽。

要完整描述不停十连的随机过程,我们要引入“状态”的概念。即便都是次十连后得到了个四星魔法少女,只要最后的保底计数不一样,那就是两个不同的状态。

表示次十连后得到了个四星魔法少女、并且保底计数为的概率,且符合归一性:

那么次十连出货个数的数学期望就是

由于十连概率的复杂性,精确解出数学期望关于抽数的解析公式已经不实际,因此退而求其次用程序执行递推计算。


递推方式

通过一次十连从一个状态转变到另一个状态的过程可以用“转移矩阵”来描述,这个转移过程是和当前的十连抽数及已出货数量无关的,重要的是转移前后两个状态的保底计数以及这次十连的出货数量

表示从保底计数经过一次十连出货个、变成保底计数的概率,,且有归一性:
那么所求概率的递推公式就是:
初始条件是:


简单探讨一下

当然,在很多情况下,递推关系可以化简,例如假如保底计数,那么说明这次十连没有出货,因此只依赖于,并且等于它乘上十次失败的概率。这也就是说:

也可以换一个角度说,假如这次十连的出货数量,那么必须满足,这一等式已经隐含了的条件,也隐含了的条件(即保底计数至少90的话这个十连必然出货才对)。这也就是说:

而对于这次十连的出货数量的时候,则需要区分出可能触发保底和没触发保底两种情况。

假如出货数量,而十连后的保底计数,这说明刚好在第十抽出了货。

  • 假如旧的保底计数,则说明是提前出货的,必定没触发保底;
  • 假如,那么它必定触发了保底,概率就是另外九抽都不出货的概率;
  • 假如,这种情况是不存在的,即,因为这意味着除了第十抽出货以外还应当有一次保底出货才对。

稍微推广一下,在出货数量的情况下,十连后的保底计数必须满足,即,此时出货的恰好是第抽。

  • 假如旧的保底计数,则说明是提前出货的,必定没触发保底;
  • 假如,那么它必定触发了保底,概率就是另外九抽都不出货的概率;
  • 假如,这种情况不存在,即,因为这意味着应当至少出货了两次才对。

类似的,假如出货数量,则十连后的保底计数必须满足,即,此时出货的是第抽和更之前的第抽()。

  • 假如旧的保底计数,则说明第抽是提前出货的,必定没触发保底;
  • 假如,那么就要计算两种情况的概率之和,一种是第抽提前出货,,按照没触发保底计算概率,另一种是第抽刚好触发保底,,按照另外九抽里面固定位置(第抽)出货一次来计算概率。

假如出货数量,则十连后的保底计数必须满足,即,此时出货的是第抽和更之前的某两抽,设第一次出货的是

  • 假如旧的保底计数,则说明第抽是提前出货的,必定没触发保底;
  • 假如,那么就要计算两种情况的概率之和,一种是第抽提前出货,,按照没触发保底计算概率,另一种是第抽刚好触发保底,,按照另外九抽里面除了固定位置(第抽)以外还有一次位于第抽和第抽之间的另一抽也出货,这样两次出货来计算概率。

推而广之,对于出货数量,则十连后的保底计数必须满足,即,此时出货的是第抽和更之前的某抽,设第一次出货的是

  • 假如旧的保底计数,则说明第抽是提前出货的,必定没触发保底;
  • 假如,那么就要计算两种情况的概率之和,一种是第抽提前出货,,按照没触发保底计算概率,另一种是第抽刚好触发保底,,按照另外九抽里面除了固定位置(第抽)以外还有次位于抽和第抽之间的抽也出货,这样次出货来计算概率。


递推公式结论

为了方便叙述,我们定义一个基础概率:

它代表10抽里面有固定位置的某抽出货、另外某抽没出货的概率。要计算没触发保底的概率,我们会用到其中的情况;要计算刚好触发保底的概率,我们会用到其中的情况。

那么前面的分类讨论就可以重新合并表述为:

  • 时,
  • 时,首先,而对于的情形则分类讨论:
    • 假如,则没触发保底,且在1个固定位置提前出货,
    • 假如,则刚好触发保底,另外九抽没出货,
    • 假如,则
  • 时,首先,而对于的情形则分类讨论:
    • 假如,则没触发保底,除了1个固定位置出货外,还有其前面个位置当中的个位置也出货,
    • 假如,那么位于第抽之前的那个出货抽的可能位置可以分成三种情况:
      • 第一种情况是第抽刚好触发保底,其余的抽位于第抽和第抽之间,有个位置,情况数量是
      • 第二种情况是第抽的位置也没出货(虽然这不符合保底机制),全出货抽都位于它后面的个位置中,情况数量是;这两种情况数量之和
      • 第三种情况是在第保底抽的位置之前就出货了,三种情况数量之和恰恰是,因此单独第三种情况数量就是
      • 因此将触发保底的概率和没触发保底的概率加起来得到

用代码表述的话就是:

def M(j, m, n):
    if j==0:
        if m!=n-10: return 0
        else: return B(0, 10)
    elif j==1:
        if n>=10: return 0
        elif m<=89+n: return B(1, 9)
        elif m==90+n: return B(0, 9)
        else: return 0
    else:
        if n>=11-j: return 0
        elif m<=88+j+n: return binom(9-n, j-1)*B(j, 10-j)
        else: return binom(m-n-91, j-2)*B(j-1, 10-j) + (binom(9-n, j-1)-binom(m-n-90, j-1))*B(j, 10-j)

程序代码

# coding=utf-8
# gacha_probabilities_v4.py

import numpy as np
import math

print("start")
p = 0.01
q = 0.02
Number = 1000
kmax = math.ceil(Number/10)
B_full = np.zeros(shape=(11))
B_part = np.zeros(shape=(10))
M_bubaodi = np.zeros(shape=(11, 10))
M_baodi = np.zeros(shape=(11, 9, 9))
def B(i, j):
    return (j/10)*(1-p)**(j-1)*(1-q)*p**i + (i/10)*(1-p)**j*p**(i-1)*q + (1-(i+j)/10)*(1-p)**j*p**i
for j in range(11):
    B_full[j] = B(j, 10-j)
for j in range(10):
    B_part[j] = B(j, 9-j)
for j in range(1, 11):
    for n in range(0, 11-j):
        M_bubaodi[j][n] = math.comb(9-n, j-1)*B_full[j]
for j in range(2, 11):
    for n in range(0, 11-j):
        for m in range(89+j+n, 100):  # m>=89+j+n>=91
            M_baodi[j][n][m-91] = math.comb(m-n-91, j-2)*B_part[j-1] + (math.comb(9-n, j-1)-math.comb(m-n-90, j-1))*B_full[j]
print("M matrix ready")

P = np.zeros(shape=(kmax+1, 10*kmax+1, 100))
P[0][0][0] = 1
P_check = np.zeros(shape=(kmax+1))
E = np.zeros(shape=(kmax+1))
for k in range(1, kmax+1):
    for i in range(10*k+1):
        for n in range(100):
            if n>=10:
                P[k][i][n] = P[k-1][i][n-10] * B_full[0]    # case j==0
            else:
                if i==0: continue
                for m in range(90+n):
                    P[k][i][n] += P[k-1][i-1][m] * B_full[1]    # case j==1, m<=89+n
                P[k][i][n] += P[k-1][i-1][90+n] * B_part[0]    # case j==1, m==90+n
                for j in range(2, min(i, 10-n)+1):    # case j>=2
                    for m in range(89+j+n):
                        P[k][i][n] += P[k-1][i-j][m] * M_bubaodi[j][n]    # case m<=88+j+n
                    for m in range(89+j+n, 100):
                        P[k][i][n] += P[k-1][i-j][m] * M_baodi[j][n][m-91]    # case m>=89+j+n
            P_check[k] += P[k][i][n]
            E[k] += P[k][i][n]*i
    print("k = %3g, E[k] = %3.10f, P_check = %3.10f" % (k, E[k], P_check[k]))

运行结果

执行了50次十连后手动暂停了程序,共500抽,图为每次十连后得到的四星魔法少女数量的数学期望。

考虑到浮点计算始终会有精度问题,计算误差会逐渐累积,所以每执行一步十连后都把此时所有可能情况的概率求和,以P_check的值与1的差值来衡量计算误差的大小。

计算结果大体上是:

100抽时

200抽时

300抽时

400抽时

500抽时

此外请注意需要乘上才是得到的pickup四星魔法少女的个数的数学期望。

一般认为在430抽时根据数学期望就已经满孔了,但数学期望是会骗人的,“430抽时抽得pickup四星个数的数学期望是4个”和“抽满4个pickup四星所需的抽数的数学期望是430抽(或者别的数字)”是完全不同的两码事,更不要提那意味着430抽内抽满和430抽抽不满的概率基本上是一半一半,并不能保证你430抽就一定能抽满孔。