滚来看看各个方面!


10

假设您有20面骰子。您开始滚动该骰子,并且必须滚动数十次才能最终滚动所有20个值。您想知道,我有多少卷才能有50%的机会看到全部20个值?n在滚动所有n面之前,我需要滚动几卷双面模具?

经过研究,您发现存在一个公式,用于计算滚动后所有值滚动的机会nr

P(r, n) = n! * S(r, n) / n**r

其中S(a, b)表示第二种斯特林数,是将一组n个对象(每个滚动)划分为k个非空子集(每侧)的方式的数量。

您还可以找到OEIS序列,我们称之为R(n),对应于最小的r,其中P(r, n)至少50%。面临的挑战是n尽可能快地计算此序列的th项。

挑战

  • 给定一个n,找到最小的 r,其中P(r, n)大于或等于0.5或50%。
  • 从理论上讲,您的代码应将任何非负整数n作为输入,但是我们将仅在范围内测试您的代码1 <= n <= 1000000
  • 对于进球,我们将采取运行所需的总时间R(n)上的投入110000
  • 我们将通过R(n)在输出中运行我们的版本来检查您的解决方案是否正确,以查看是否P(your_output, n) >= 0.5P(your_output - 1, n) < 0.5,即您的输出实际上r是给定输出的最小值n
  • 您可以S(a, b)在解决方案中使用任何定义。Wikipedia有几个定义可能会对您有所帮助。
  • 您可以在解决方案中使用内置函数,包括那些可以计算S(a, b)甚至P(r, n)直接计算的函数。
  • R(n)尽管这两个都不是硬性限制,但您最多可以对1000个值和一百万个斯特林数进行硬编码,并且如果可以说服您提高或降低它们,则可以更改。
  • 你并不需要检查每一个可能的r之间nr我们要找的,但你需要找到最小的r,不是任何r地方P(r, n) >= 0.5
  • 您的程序必须使用Windows 10上可自由运行的语言。

用于测试您的解决方案的计算机的规格为i7 4790k, 8 GB RAM。感谢@DJMcMayhem提供了用于测试的计算机。可以随意添加您自己的非官方时间,以供参考,但DJ可以对其进行测试之后,将提供官方时间。

测试用例

n       R(n)
1       1
2       2
3       5
4       7
5       10
6       13
20      67       # our 20-sided die
52      225      # how many cards from a huge uniformly random pile until we get a full deck
100     497
366     2294     # number of people for to get 366 distinct birthdays
1000    7274
2000    15934
5000    44418
10000   95768
100000  1187943
1000000 14182022

如果您有任何疑问或建议,请告诉我。祝你好运,优化!


1
@JonathanAllan Knew我应该选择其他措辞。感谢您的注意。
Sherlock17年

Answers:


7

Python + NumPy,3.95秒

from __future__ import division
import numpy as np

def rolls(n):
    if n == 1:
        return 1
    r = n * (np.log(n) - np.log(np.log(2)))
    x = np.log1p(np.arange(n) / -n)
    cx = x.cumsum()
    y = cx[:-1] + cx[-2::-1] - cx[-1]
    while True:
        r0 = np.round(r)
        z = np.exp(y + r0 * x[1:])
        z[::2] *= -1
        r = r0 - (z.sum() + 0.5) / z.dot(x[1:])
        if abs(r - r0) < 0.75:
            return np.ceil(r).astype(int)

for n in [1, 2, 3, 4, 5, 6, 20, 52, 100, 366, 1000, 2000, 5000, 10000, 100000, 1000000]:
    print('R({}) = {}'.format(n, rolls(n)))

import timeit
print('Benchmark: {:.2f}s'.format(timeit.timeit(lambda: sum(map(rolls, range(1, 10001))), number=1)))

在线尝试!

怎么运行的

它使用Prn)的闭式序列及其相对于r的导数进行重新排列以实现数值稳定性和矢量化,以牛顿法搜索r使得Prn)= 0.5舍入r在每个步骤之前为整数,直到该步骤将r移动小于3/4。有了一个很好的初步猜测,通常只需要进行一到两次迭代。

x i = log(1 − i + 1)⋅((n / Ñ)=日志((ñ - )/ Ñ
CX =日志(ñ /((!ñ - ! - 1)⋅ Ñ + 1
ÿ = CX + cx ni − 2cx n − 1 = log binom(ni + 1)
z i =(-1)i + 1⋅binom(ni − 1)/ nr
1 + ∑ z i = n!⋅ 小号[R Ñ)/ ñ - [R = P[R Ñ
Ž X + 1 =(-1) + 1 ⋅binom(Ñ + 1)⋅((ñ - - 1) / nr log((ni − 1)/ n
Σ ž X + 1 = d / d - [R P[R Ñ


1
整体答案出色!首先,我应该意识到这0.366512log很久以前的事了。将-log(log(2)在我的下一个迭代中使用。其次,使用牛顿法的想法也非常聪明,我很高兴看到它如此有效。第三,我几乎肯定会偷exp(log(binom(n, i+1)) + r * log((n-i-1)/n)):P Kudos一个很好的答案!:D
Sherlock17年

1
我已经添加了官方时间!好的回答顺便说一句:)
詹姆斯

2
我真的很困惑 我将numpy导入更改为from numpy import *,由于某种原因,时间基本上降为0 ... 在线尝试
notjagan

@notjagan缓存命中了吗?
NoOneIsHere

1
我想对以下几件事表示歉意:1)当我试图寻求改进时,我窃您的答案;2)没有适当地承担它,而只是试图解决我的答案;3)道歉花了很长时间。我非常生气,一开始我只是放弃了这一挑战。稍作赔偿,我想很公平地告诉你,我对这个答案的主要改进是从牛顿方法变为增量方法r,因为您的初始近似值已经相当不错了。希望再次与您在PPCG中相见,并为一切感到抱歉。
Sherlock9年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.