芯片与武器组合的概率分布

芯片与武器组合的概率分布

> 编程

如果你用某套系统来计算对对手造成伤害的最佳物品(芯片和武器)组合,那你多半也有一套(属于自己的)办法来估算所研究组合的伤害:如果你比较悲观,可能会取每件物品最小伤害之和;如果你比较中立,也许会取平均伤害之和……

本页的目的,是介绍一些概率概念,让你能更细致地研究这样一套“combo”(组合)的伤害概率。

警告:本文需要一点数学功底。至少要有高中的积分知识,最好还有一点概率知识。

目录

> 1. 动机 >2. 使用芯片或武器时的随机取值 >3. 直接方法 >4. 连续方法 >    4.1. 基本原理 >    4.2. 单件物品对应的概率密度 >    4.3. 卷积 >        4.3.1 引入:离散情形 >        4.3.2 连续情形 >        4.3.3 几条性质 >    4.4. 在 LeekScript 中的实际计算 >        4.4.1 Leek Wars 中的组合:场景带来的简化 >        4.4.2 符号计算:多项式 >        4.4.3 分段定义的函数 >        4.4.4 卷积 >        4.4.5 暴击:两个矩形之和 >5. 固定护盾与固定伤害物品的处理:混合分布 >    5.1. 固定护盾:0 处的原子 >        5.1.1 混合分布 >        5.1.2 0 处的狄拉克分布 >    5.2. 固定伤害物品与暴击:其他位置的原子 >        5.2.1 暴击与原子 >        5.2.2 任意一点处的狄拉克分布 >    5.3. 在 LeekScript 中的计算 >6. 分布函数与概率工具 >    6.1 定义与计算 >    6.2 用法 >7. 备注

动机

算出一套组合造成的平均伤害并不难。不过你可能想要比平均值更精确的信息:如果你想保证至少打出多少伤害(比如为了有很大机会击杀),平均值帮不了你多少。

举个例子:你的对手只剩 430 点生命值,有 155 点固定护盾,而你有 400 点力量、没有敏捷(因此不可能打出暴击)。你挑出了两套可以击杀对手的组合:用三次犀牛,或者用两次步枪

确实,三次犀牛造成的最大伤害为: 3 * (64 * (1 + 400/100) - 155) = 495 而两次步枪造成的最大伤害为: 2 * (79 * (1 + 400/100) - 155) = 480 两种情况都超过了 430,而且TP消耗相近(假设你有 15 点,并且手上拿着犀牛)。

平均伤害用同样的方法计算,犀牛和步枪都得到 450。从平均值只能推出:犀牛和步枪杀死对手的概率都超过 50%。但这并不能帮我们挑出更可能致命的那一套组合。

所以,要想把击杀对手的概率最大化,我们需要更精确的信息。

> 剧透:这里是步枪的机会更大,击杀概率 94.4%,而犀牛只有 90.4%。

这个例子比较简单,看上去也许没多大意思,甚至相当直观。而下面介绍的工具能处理更多样的组合,还能把暴击概率考虑进来——暴击对伤害概率的影响有时相当难以捉摸。

最后要说的是,这套概率分析还有别的用途(比如治疗:可以在“治疗量不超过缺失生命值”这类条件下寻找最佳治疗组合)。我在这里只是介绍工具,怎么用就看你了。

使用芯片或武器时的随机取值

每次使用武器或芯片时,游戏对所有效果只做一次且仅一次随机取值(在 0 和 1 之间)。每个效果的数值由下式给出: valeur = valeur_min + tirage * (valeur_max - valeur_min) 其中 tirage 就是取到的那个 0 到 1 之间的数。

这个随机取值是按均匀概率在 0 和 1 之间抽取一个浮点数:没有哪个浮点数比其他浮点数更容易出现。

直接方法

使用一件物品时,我们可以算出每个可能结果出现的概率:毕竟取值是均匀的,所以每个结果出现的概率都相同(两端的极值结果除外,它们的概率其实只有其他结果的一半)。 确实,只要四舍五入前的结果落在 k-0.5 和 k+0.5 之间,数值 k 就会出现;最小值和最大值除外:取值不可能低于最小值,所以四舍五入后得到最小值的浮点数少了一半(只有 k 到 k+0.5 之间的那些),最大值同理(这次是取值不可能高于它,道理一样)。

这样,在 400 力量下使用火花芯片,伤害会落在 40 到 80 之间,因此这两个值之间的每个整数概率都是 1/40 = 0.025(40 和 80 除外,它们的概率是 0.0125)。

而如果想连着用好几件物品,理论上也可以算出每个可能结果的概率。 例如,在没有力量、没有敏捷、目标也没有护盾的情况下,对于由两枪手枪组成的组合,要算出造成 33 点伤害的概率,可以找出所有凑成 33 的方式,再把每种方式的概率加起来。 于是计算过程大致如下:

你大概也猜到了:就算有更快的办法,这个方法还是很费劲。毕竟我挑的这两件物品伤害范围都很小,而且没给我的韭葱加力量,就是为了不用处理太多情况。想象一下(或者算一算!)用 500 力量抡斧头时会有多少种可能的数值吧!

所以这个方法在实践中并不好用。从算法上说是做得到的,但只要物品一多、或者伤害范围一大,开销就可能很高。

连续方法

要应付数量如此庞大的可能取值,有一个办法:假装随机取值可以是 0 到 1 之间的任意实数,而不只是浮点数。严格来说这并不对,但考虑到 0 到 1 之间可用的浮点数非常多(受编码方式所限,它必然只有有限个,但数量依然十分可观),两种情形之间的差别其实很小。

我们要用的工具叫作密度函数:这种函数用来描述一个在实数集(或其中一部分,这里是一个区间)里取值的随机量的行为。

> 给爱较真的数学党:好吧好吧,严格来说还得加上几个条件……后面我会讲带原子的分布,至于奇异概率分布我压根不会提,反正嘛……在这里它意义不大。

基本原理

好……先来一点数学,把这些概念引进来。

概率密度是一个处处为正、积分等于 1 的函数 f。它按下面的方式描述随机变量 X(这里就是一套组合造成的伤害)的行为:

> X 取值落在两个数 a 和 b 之间的概率,由 f 在 a 到 b 上的积分给出。换句话说,!密度的定义

示例(这里用的是无属性时两次火花组合对应的密度):

!两次火花的组合

注意函数 f 确实处处为正,而且它在两个端点之间的积分确实等于 1。

算出 19.5 到 25.5 之间的积分,我们就知道这套组合有 0.574…(也就是 57.4%)的概率造成 19.5 到 25.5 点伤害,因此四舍五入后就是 20 到 25 点伤害。

对所用函数的这两个条件,可以这样解释:

如果我们有办法表示某套组合对应的这样一个函数(这正是本文后面要做的事),就能从中推出不少信息,例如:

!组合取到精确值

!组合至少造成 d 点伤害

所以思路是:不再用“每一个可能取值加上对应概率”这种处理起来很笨重的方式来表示一个概率分布,而是用一个能从中推出各种信息的函数来表示。 而在 Leek Wars 的概率问题里,这些函数操作起来其实相当简单(见下文的实践部分)。

单件物品对应的概率密度

好了,道理讲得挺好,可在我们关心的情形里(也就是游戏中的伤害概率),这些密度函数到底怎么找?

先从单件物品说起,暂时不考虑韭葱的属性(敏捷、力量、护盾)。

既然物品伤害范围内的所有浮点数(至少是所有可能出现的浮点数……)概率都相同,我们就用矩形密度函数来表示物品:

!火花的矩形

注意矩形的高和宽是关联的:积分必须等于 1,所以矩形的高度由物品的伤害数值决定: hauteur = 1/(valeur_max - valeur_min) 对于没有力量、没有敏捷、对面也没有护盾的火花,这就是 1 / (16 - 8) = 1/8 = 0.125,在上面的图里也能看到这一点。

把力量考虑进来并不麻烦:只要算出可能的伤害范围,再画出对应的矩形即可。于是,同样是火花,但带 400 点力量时,最小伤害是 40,最大伤害是 80,因此矩形从 40 延伸到 80,高度为 1/40 = 0.025:

!带力量的火花矩形

要在 LeekScript 里操作这类对象,我们需要存两个数据(比如最小值和最大值,因为矩形的高度可以由它们推出)。不过后面会发现,能操作一类更一般的函数会更有用。所以关于 LS 实现的思路,我留到后面再讲。

注意,固定伤害的物品没法用矩形表示。如果你没有暴击机会,那没问题:伤害是固定的,单独处理就行。固定伤害但有暴击机会的情况,后面会讲。

卷积

维基百科的《卷积》条目

于是问题来了:如果我连着用好几件物品,怎么从每件物品各自的密度算出整套组合对应的密度函数?

引入:离散情形

为了举例,我们先回到离散情形,仍然用前面那套两枪手枪的组合(没有力量、没有敏捷,目标也没有护盾)。

如果我把一枪能打出的各个数值的概率画在图上,得到的是这样:

!手枪的离散分布

(注意:我在这里暂时回到了逐个取值列出概率的表示法,这并不是密度函数。)

如前所述,造成 33 点伤害的概率等于取到 (15 ; 18)、(16 ; 17)、(17 ; 16) 和 (18 ; 15) 这几组数值的概率之和。

于是可以把这个计算画成下面这种图:

!离散卷积

第一张图给出第一枪每个数值的概率,其中黑色的那些太大了,凑不出 33 这个和。第二张图给出的则是 (33 - 第二枪结果) 这些数值的概率:这样一来,如果取值让第一枪打出 15、第二枪打出 18,那么两张图上对应的点就正好上下对齐(都在横坐标 15 处)。

于是计算时,只要把上下对齐的两个值相乘再求和:

第一张图是直接画出来的,那第二张呢?它可以通过下面的几何操作得到:如果想要的和是 S(这里是 33),就先把第一张图关于纵轴作对称,再把得到的图向右平移 S 个单位。

!离散卷积的对称

!离散卷积的平移

连续情形

那么概率密度的情形呢?同样的方法就能解决我们的问题。只不过现在要把它改写成针对密度、而不是针对离散分布的说法,原理还是一样的。

取两个随机变量 X 和 Y,密度函数分别为 f 和 g,再取一个数 s,若要求 X+Y 取值为 s 的概率,做法如下:

例如,取下面画出的两个密度函数,并令 s = 7:

!连续卷积1

!连续卷积2

当然,结果取决于我们想要的和 s。于是把“对每个数 s 给出上述计算结果”的那个函数称为 f 与 g 的卷积,记作 f * g。

可以给出它的公式:!卷积公式 不过在实践中真正有用的,还是上面那套构造。

到目前为止,我们只说明了怎么算出 f * g 在某一点 s 的值。如果想把 f * g 当作这两个随机变量之和的密度函数来用,就需要一个对任意 s 都成立的表达式。

根据所考虑函数的形状,这一步可能或难或易。在这里,我们要处理的函数都比较简单,但有一个特点:它们是分段定义的。在前面的例子中,可以这样做:

这里最关键的性质是(前面多少已经说过了,现在把它讲清楚):

> 设 X 和 Y 是两个相互独立的实随机变量,分别由密度 f 和 g 描述。那么它们的和 S = X + Y 也是一个有密度的随机变量,密度为 f * g。 (借助分布的概念,这一点还能推广到混合分布,见后文,不过这里我们主要关心的还是密度。)

卷积有几条可能派得上用场的性质。

> 它是可交换的:f * g = g * f。

所以你想按什么顺序做卷积都行,无所谓。

> 它对加法表现良好:f * (g+h) = (f * g) + (f * h)。

所以可以把函数拆成若干个简单部分之和,对每一部分分别做卷积,再把结果加起来。

> 它对常数倍表现良好:f * (a x g) = a x (f * g)。

所以可以把乘性常数从卷积里“提”出来。

> 它是可结合的:(f * g) * h = f * (g * h)。

有了结合律和交换律,就可以按任意顺序一个接一个地做卷积(在我们的情形下,这相当于说一套组合的伤害概率与使用物品的先后顺序无关——当然,前提是中途不使用增益)。

在 LS 中的实际计算

Leek Wars 中的组合:场景带来的简化

先说一个关键的事实(因为它能大大简化计算):我们每件物品的概率密度都是矩形函数,而若干个这类函数的卷积,必然是一个分段定义、且每一段都是多项式的函数。

更确切地说,k 个矩形的卷积总是由次数不超过 k-1 的多项式段组成。

此外,当我们往组合里逐个添加物品时,做的就是与矩形的卷积,而这种卷积算起来相当省事:在前面介绍的构造里,可以根据想要的和 s 去“移动”矩形,于是要算的积分永远只是已算出的密度在两个依赖于 s 的边界之间的积分,再乘上矩形的高度:

!三次火花的卷积

这里的例子中,g 是无力量时两次火花得到的密度函数,矩形则对应第三次使用同一张芯片。

因此,针对 Leek Wars 的情形,我们需要:

符号计算:多项式

这里我只讲大原则,不提供现成的实现。你可以按自己喜欢的方式来改写(想搞面向对象就写一个 Polynome 类,不想的话就直接操作数组,或者用任何你觉得合适的做法)。

要表示一个多项式函数 f(x) = a_n * x**n + ... + a_1 * x + a_0,只需存下它的系数。于是可以用数组 [a_0, a_1, ..., a_n] 来表示 f。

在此基础上,实现那些有用的数学运算并不难:

f + g 用形如 [a_0 + b_0, ..., a_p + b_p, a_(p+1), ..., a_n] 的数组表示(这里我取了 `p

计算卷积时得到的函数是分段定义的:因此需要在 LS 里表示这类函数。为此可以存一个分段的列表(在我们的情形里每段都是多项式),并带上各自有效定义域的最小值和最大值。同样,做法有好几种(面向对象、直接用数组……),我这里只讲需要能做到哪些操作:

最后,难点就在这里:必须能计算这样一个函数与一个矩形的卷积。

卷积

如果前面这些你都写好了,工作已经完成了一大半。计算分段多项式函数与矩形的卷积,难点主要在于确定结果函数的分界点。

举一个仍然比较简单的计算例子(至少是开头部分……):已有两次火花的概率密度 g,现在再加一枪手枪(没有力量),它的密度是矩形 f。我们一步步算出 2 次火花 + 1 枪手枪这套组合对应的新密度 g * f(写成 f * g 也一样):

暴击其实算不上什么难题。事实上,一件可能打出暴击的物品,其概率密度由两个矩形组成。

更确切地说:考虑一件暴击概率为 p 的物品。设 f 是这件物品在没有暴击机会时对应的矩形函数,最小值为 a,最大值为 b。那么考虑暴击后的概率密度就是 (1-p)f + pg,其中 g 是最小值为 CRITICAL_FACTOR * a、最大值为 CRITICAL_FACTOR * b 的矩形。

而它与某个密度函数 h 的卷积可以写成: !暴击公式 于是只要做两次 h 与矩形的卷积,再把两个分段定义的函数加起来就行了。

固定护盾与固定伤害物品的处理:混合分布

到目前为止,我还没讲对手有护盾时会怎样。百分比护盾完全不成问题,它只是给伤害加了一个乘数,和力量一样,处理方式也一样(矩形会更靠近 0、幅度更小,仅此而已)。固定护盾就真的麻烦了。

因为它会逼我们去考虑这样一种概率:某个确切的数值出现的概率不为零。 对不熟悉的人说明一下:是的,乍看有点奇怪,但在概率密度的框架下,一个确切数值出现的概率永远是……零。我在介绍这个工具时说过,要得到组合造成 a 到 b 点伤害的概率,用的是密度从 a 到 b 的积分。而在我们关心的情形里,组合四舍五入后造成 s 点伤害的概率,可以取 s - 0.5s + 0.5 之间的积分。但在四舍五入之前,正好落在某个确切和上的概率,是从 ss 的积分……也就是恒为 0。

打个比方:如果我给你一块木板、一把锯子和一把卷尺,让你锯出 50 厘米长的一段,你(只要手不太差)会得到一块大约 50 厘米的木板。但如果换一把更精密的量具,你这块木板有没有可能正好是 50 厘米?精确到毫米呢?精确到微米呢?除非你是锯木大师,否则只要我量得够细,总能找出一点偏差。这里的道理是一样的:我可以有非零的概率在四舍五入后命中某个值,但不考虑四舍五入的话,一点机会都没有。

固定护盾:0 处的原子

然而,固定护盾偏偏会带来一个讨厌的后果:它让 0 这个确切数值有了非零的出现概率。如果我用一次火花(没有力量)去打一个戴着头盔(没有抗性)的对手,我很可能打出 0 点伤害。要想打出(非零的)伤害,游戏的随机取值必须给出 15.5 到 16 之间的伤害,四舍五入成 16,再扣掉护盾后变成 16 - 15 = 1。其他所有取值下,伤害都是 0。

所以我们得处理这些“造成 0 点伤害”的非零概率,而光靠概率密度是做不到的:不存在哪个密度函数能让它从 0 到 0 的积分非零。

> 给数学党和物理党:是的,我接下来要讲狄拉克分布,但它并不是一个函数。

混合分布(离散部分 + 由“密度”描述的部分)

所以,我们要处理的概率分布由一个密度函数、外加某些数值上的概率共同给出(尤其是因为护盾而出现的零,不过我们会看到固定伤害的武器也会造成同类现象)。

> 如果某个概率分布在点 a 处的概率不为零,我们就说这个分布在 a 处有一个原子

一般来说,可以把一个概率分布拆成几部分(我略过需要验证的那几个假设……这里没问题):一个离散部分、一个由密度描述的部分,还有一个跟我们无关的古怪部分。有兴趣的话……可以看拉东–尼科迪姆定理(数学开始有点复杂了,不过理解这个定理对后文完全不是必需的,我只是顺带提一句)。

从现在起,我可能会把积分不等于 1 的函数也叫作“密度”。这是用词不严谨!但想法是一样的:用一个函数来表示分布中(绝对)连续的那一部分。

好,不去纠缠复杂的细节……这里除了描述原子之外概率的密度,还要保留原子本身以及它们对应的概率。并且在往组合里加入新元素时,把这些信息传递下去。

举个例子:两次火花,带 400 点力量,对手有 60 点固定护盾(百分比护盾为 0)。 没有护盾时,一次火花会造成 40 到 80 点伤害,用下面这个矩形表示 !400力量下的1次火花

但加上 60 点固定护盾后,伤害减少 60,矩形因此向左平移 60。而伤害不能为负:矩形的左半部分就被“压”到了 0 这个值上。于是得到的概率由两样东西表示:

!400力量60固定护盾下的1次火花

加上第二次火花。这时要分几种情况:

于是我们保留下来的信息是:

!400力量60固定护盾下的2次火花

可以注意到,得到的函数并不连续。这和没有原子的情形不同:那时每加一件物品,函数的光滑程度就会提高,而现在这已经不是必然的了!

0 处的狄拉克分布

从数学上说,可以用一个奇怪的工具来表示这个对象:0 处的*狄拉克分布*,记作 δ。它是这样一个对象(不可能是函数……):除了 0 以外处处为 0,而积分等于 1。我不在这里做严格定义,但有了这个“分布”,就能给“打出 0 的概率”这个概念一套更完备的形式:可以说这个概率由 0.25δ + g 表示(其中 g 就是上面画的那个函数)。

这个狄拉克分布有一条对卷积非常有用的性质:对任意函数 h,总有 δ * h = h * δ = h。

于是前面那四种情况可以归结成一个公式: !狄拉克的卷积 其中确实出现了打出 0 的概率 0.25,同时也得到了一个计算其余情况对应函数的简单公式。

注意,在计算一套组合的伤害概率时,只要你加进一件不可能打出 0 的物品,打出 0 点伤害的概率就消失了(很合理,不是吗?)。因此,只有在加入这样一件物品之前才会有 δ 项,之后就只剩下一个真正的密度(积分为 1)了。

固定伤害物品与暴击:其他位置的原子

还有一种情形会让某个确切数值的概率不为零:固定伤害的物品(在我写这篇文章时是武士刀非法榴弹发射器恶魔打击惩戒)。

没有暴击机会时,可以用一个相当简单的办法:算出所研究组合的固定伤害,再把其他物品得到的密度函数平移这么多即可。但暴击概率让这个办法失效了,因为“固定”伤害其实不再固定(有两个可能取值)……于是我们就有了两个原子。

暴击与原子

一种做法是,每加入一件固定伤害的物品,就分两种情况:

然后把这样得到的两个概率分布按暴击概率加权求和。

因此要能处理两个可能带原子的分布之和:结果分布会在任一分布原本有原子的所有点上都有原子。

举个例子,好看得更清楚:火花 + 武士刀的组合,400 点力量、400 点敏捷(因此暴击概率为 0.4),目标韭葱有 50 点固定护盾。

!带暴击和固定护盾的火花

!带暴击和固定护盾的火花+武士刀

因此,这套组合对应的概率分布里有两个原子。如果再加第三件物品,就要分三种情况:

任意一点处的狄拉克分布

同样,可以用放在合适位置的狄拉克分布把这件事形式化:位于点 a、概率为 p_a 的原子,可以表示成 a 处的一个狄拉克峰,按概率 p_a 加权,记作 p_a δ_a

前面的计算于是可以这样写:

于是这套组合的分布为: !火花+武士刀带暴击和固定护盾的计算

而与位于点 a 的狄拉克峰做卷积,相当于向右平移 a。于是我们确实得回了前面算出的那个概率分布。

在 LS 中的计算

如果你已经写好了卷积,最难的部分照理说已经做完了。这里只需要再加一层:概率分布不再只用一个分段多项式函数表示,而是在这样一个函数之外,再加上一组原子及其对应的概率。

所以要写的,就是一套记录这些原子的办法,再加上一个把原子考虑在内的卷积函数:它要分几种情况处理(原子 * 原子、原子 * 密度、密度 * 原子、密度 * 密度),然后把得到的各个分布相加。

有了你已经写好的那些工具——按给定向量平移函数、两个密度的卷积、分段多项式函数求和——你应该能搞定。

分布函数

至此我们有了办法,把一个随机变量(它表示造成的伤害)的概率分布,写成一组原子加上一个“密度”函数的形式(说“密度”其实不太准确,它的积分是 1 减去各原子的概率,更确切的说法是:它是一个有密度的分布与一个离散分布的线性组合……这里就不深究了)。

在此基础上可以提取好几种信息。前面说过,有密度时,造成 a 到 b 点伤害的概率由密度在 a 到 b 上的积分给出。而这里有了原子,就要把“密度”在 a 到 b 上的积分,与位于 a 和 b 之间的那些原子的概率加起来。

与其反复计算积分,不如算出这个分布的所谓分布函数

定义与计算

> 随机变量 X 在 R 上的概率分布的分布函数,是这样一个函数:它给每个数 x 对应变量 X 取到小于 x 或等于 x 的值的概率。 也就是说:!分布函数的定义

如果一个概率分布由密度 D 给出,那么分布函数就是 D 的那个在 -无穷处极限为 0 的原函数。 > 给爱较真的数学党:好吧……严格意义上它并不是原函数。如果密度是一个矩形,分布函数在整个 R 上并不可导,所以它算不上原函数……

如果概率分布既有“密度”又有原子,就要分几步进行:

举个例子,用前面那套火花 + 武士刀的组合,我们有一个函数 D: !带暴击和固定护盾的火花+武士刀 以及两个原子,一个在 335 处、概率 0.09,另一个在 450.5 处、概率 0.06。 于是分布函数由下面两部分构成:

!火花+武士刀的分布函数1

!火花+武士刀的分布函数2

用法

有了分布函数,就能相当容易地算出一些有用的概率。如果 F 是某个概率分布的分布函数,那么: !由分布函数得到的概率

因此,把这个函数用符号计算求出来,就不必每次需要一个概率时都去算积分了。

备注

用得到的分布去计算伤害的期望,是白白把事情搞复杂了:整套组合伤害的期望,就是各件物品单独期望之和。

同样,由于使用物品时的各次取值是相互独立的,标准差可以直接由各件物品的标准差 s1、s2、…… 用公式 s = sqrt(s12 + s22 + ...) 算出

所以掌握这个分布的价值,并不在于计算这些指标。

___ ___ 注:插图是用 Geogebra 软件制作的。 ___ 参考文献:

Philippe Barbe 与 Michel Ledoux,Probabilité,EDP Sciences(2007)

如果你的数学底子已经很好,这里有一本测度论的经典参考书(提醒一下,它有点硬): Walter Rudin,Analyse réelle et complexe,Masson 出版(1975、1977)或 Dunod 出版(1998) 英文版: Walter Rudin,*Real and complex analysis*,McGraw-Hill(1987)