计算公平骰子掷骰的概率(非指数时间)

时间:2018-06-05 00:09:14

标签: algorithm go probability dice

这方面的变化是非常常见的问题,但我所有的谷歌都让我感到难过。我想计算一个公平骰子掷骰的几率,但我想这样做有效。有很多关于如何做到这一点的例子,但是我发现的所有算法在计算上都非常昂贵(指数时间)来处理大量有许多方面的骰子。

简单问题:计算x侧面骰子上掷骰的几率。

简单解决方案:创建滚动的n-ary Cartesian积,求和每个积,计算总和为目标的次数,做一点划分并瞧瞧。

Go中的简单解决方案示例: https://play.golang.org/p/KNUS4YBQC0g

简单解决方案完美无缺。我将其扩展为允许丢弃最高/最低n面的情况,结果可以进行现场测试。

但请考虑{Count: 20,Sides: 20,DropHighest: 0,DropLowest:0, Target: 200}

如果我使用之前的解决方案进行评估,那么我的“表格”会有104个奇怪的septillion单元格,并且很容易使CPU最大化。

是否有更有效的方法来计算大量骰子的概率?如果是这样,它是否可以解释更复杂的“成功”条件选择,例如放弃一些骰子?

由于这个漂亮的网站的存在,我确信这是可能的:https://anydice.com/program/969

编辑:

对我来说最有效的解决方案是David Eisenstat的回答,我将其移至:https://play.golang.org/p/cpD51opQf5h

2 个答案:

答案 0 :(得分:2)

这里有一些代码可以处理掉低和高滚动。很抱歉切换到Python,但我需要简单的bignums和memoization库来保持我的理智。我认为复杂性类似于O(count^3 sides^2 drop_highest)

此代码的工作方式是将count个骰子的可能性空间除以sides个边,显示最多数量(count_showing_max)。有binomial(count, count_showing_max)个方法可以在唯一标记的骰子上实现这样的滚动,因此multiplier。给定count_showing_max,我们可以计算出有多少最大骰子因为高而被剔除,有多少骰子因为低而被丢弃(它发生)并将此总和加到剩余骰子的结果中。

#!/usr/bin/env python3
import collections
import functools
import math


@functools.lru_cache(maxsize=None)
def binomial(n, k):
    return math.factorial(n) // (math.factorial(k) * math.factorial(n - k))


@functools.lru_cache(maxsize=None)
def outcomes(count, sides, drop_highest, drop_lowest):
    d = collections.Counter()
    if count == 0:
        d[0] = 1
    elif sides == 0:
        pass
    else:
        for count_showing_max in range(count + 1):  # 0..count
            d1 = outcomes(count - count_showing_max, sides - 1,
                          max(drop_highest - count_showing_max, 0),
                          drop_lowest)
            count_showing_max_not_dropped = max(
                min(count_showing_max - drop_highest,
                    count - drop_highest - drop_lowest), 0)
            sum_showing_max = count_showing_max_not_dropped * sides
            multiplier = binomial(count, count_showing_max)
            for k, v in d1.items():
                d[sum_showing_max + k] += multiplier * v
    return d


def main(*args):
    d = outcomes(*args)
    denominator = sum(d.values()) / 100
    for k, v in sorted(d.items()):
        print(k, v / denominator)


if __name__ == '__main__':
    main(5, 6, 2, 2)

答案 1 :(得分:1)

您可以通过将变量x中的以下多项式相乘来计算y Z侧面骰子的总和分布:

(Z + Z^2 + ... + Z^x)^y / x^y.

例如,对于两个六面骰子:

(Z + Z^2 + ... + Z^6)^2 / 6^2
  = (Z + Z^2 + ... + Z^6) * (Z + Z^2 + ... + Z^6) / 36
  = (Z^2 + 2Z^3 + 3Z^4 + 4Z^5 + 5Z^6 + 6Z^7 + 5Z^8 + 4Z^9 + 3Z^10 + 2Z^11 + Z^12) / 36,

因此,您可以将6系数Z^65/36)的概率读出来。

对于三个“双面”骰子:

(Z + Z^2)^3 / 2^3 = (Z + Z^2) * (Z + Z^2) * (Z + Z^2) / 8
                  = (Z^2 + 2Z^3 + Z^4) (Z + Z^2) / 8
                  = (Z^3 + 3Z^4 + 3Z^5 + Z^6) / 8,

所以获得总和4的概率是Z^43/8)的系数。

您可以使用学校算法来获得此问题的多项式算法。经过轻微测试的Go代码:

package main

import "fmt"

func dieRolls(x, y int) map[int]float64 {
    d := map[int]float64{0: 1.0}
    for i := 0; i < x; i++ {
        d1 := make(map[int]float64)
        for j := 1; j <= y; j++ {
            for k, v := range d {
                d1[k+j] += v / float64(y)
            }
        }
        d = d1
    }
    return d
}

func main() {
    for k, v := range dieRolls(2, 6) {
        fmt.Printf("%d %g\n", k, v)
    }
    fmt.Printf("\n")
    for k, v := range dieRolls(3, 2) {
        fmt.Printf("%d %g\n", k, v)
    }
}

运行代码:https://play.golang.org/p/O9fsWy6RZKL