모든면을 볼 롤!


10

20면의 주사위가 있다고 가정 해 봅시다. 다이를 굴리기 시작하고 마지막으로 20 개의 값을 모두 굴리기 전에 수십 번 굴려야합니다. 20 개의 값을 모두 볼 확률이 50 %가되기 전에 얼마나 많은 롤이 필요한지 궁금합니다. 그리고 n모든 n면 을 굴리기 전에 몇 개의 양면 주사위를 굴려야 합니까?

일부 조사 후 롤 후 모든 값을 굴릴 가능성 을 계산하기위한 공식이 있음을 알게됩니다 .nr

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

여기서 두 번째 종류의 스털링 번호 , n 개의 객체 세트 (각 롤)를 비어 있지 않은 서브 세트 (각면)로 분할하는 방법의 수를 S(a, b)나타냅니다 .

또한 우리가 부르는 OEIS 시퀀스 는 50 % 이상인 R(n)가장 작은 r위치에 해당합니다 P(r, n). 문제는 n이 시퀀스의 용어를 가능한 빨리 계산하는 것 입니다.

도전

  • 가 주어지면 50 % 이상인 가장 작은 곳을 n찾으십시오 . rP(r, n)0.5
  • 코드는 이론적으로 음이 아닌 정수 n를 입력 으로 처리해야 하지만의 범위 내에서만 코드를 테스트합니다 1 <= n <= 1000000.
  • 점수를 매기 R(n)려면를 1통해 입력 을 실행 하는 데 필요한 총 시간이 걸립니다 10000.
  • 솔루션은 우리의 버전을 실행하여 올 경우 우리는 확인합니다 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)직접 계산하는 솔루션을 포함하여 솔루션에 내장 기능을 사용할 수 있습니다 .
  • 최대 1000 개의 값 R(n)과 백만 개의 스털링 숫자 를 하드 코딩 할 수 있지만이 중 어느 것도 하드 한계가 아니며 값을 올리거나 내릴 때 설득력있는 주장을 할 수 있으면 변경할 수 있습니다.
  • 당신 과 우리가 찾고 있는 것 r사이에 가능한 모든 것을 점검 할 필요는 없지만, 어디에서가 아니라 가장 작은 것을 찾아야합니다 .nrrrP(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 나는 다른 표현을 선택했을 것입니다. 고마워요
Sherlock9

답변:


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)))

온라인으로 사용해보십시오!

작동 원리

이것은 P ( r , n )에 대한 폐쇄 형 계열 과 수치 안정성 및 벡터화를 위해 재 배열 된 r에 대한 파생 형을 사용하여 P ( r , n ) = 0.5가 되도록 r에 대한 뉴턴의 방법 검색을 수행합니다. R 각 단계 전에 정수, 단계 이동할 때까지 r에 보다 3/4. 초기 추측이 좋을 때 일반적으로 한두 번의 반복이 필요합니다.

x i = log (1 − i / n ) = log (( ni ) / n )
cx i = log ( n ! / (( ni − 1)! ⋅ n i + 1 )
y i = cx i + cx ni − 2cx n − 1 = 로그 이항 ( n , i + 1)
z i = (-1) i + 1 ⋅ binom ( n ,i + 1) ⋅ (( ni − 1) / n ) r
1 + ∑ z i = n! ⋅ S ( r , n ) / n r = P ( r , n )
z ix i + 1 = (-1) i + 1 ⋅ binom ( n , i + 1) ⋅ (( ni − 1) / n ) r 로그 (( ni − 1) / n)
z ix i + 1 = d / d r P ( r , n )


1
전체 답변에서 훌륭한 작품! 첫째, 그 실현해야 0.366512이었다 log뭔가 세의 전. -log(log(2)다음 반복에서 사용할 것 입니다. 둘째, Newton의 방법을 사용하는 아이디어도 매우 영리하며 이것이 잘 작동한다는 것을 알게되어 기쁩니다. 셋째, 나는 거의 확실히 훔칠 exp(log(binom(n, i+1)) + r * log((n-i-1)/n))것입니다. : D
Sherlock9

1
공식 타이밍을 추가했습니다! 좋은 답변 BTW :)
James

2
정말 혼란 스러워요. numpy가져 오기를 변경했으며 from numpy import *어떤 이유로 시간이 기본적으로 0으로 떨어졌습니다. 온라인으로 사용해보십시오 .
notjagan

@notjagan 캐시 적중?
NoOneIs 여기

1
몇 가지 사항에 대해 사과하고 싶습니다. 1) 개선을 시도했을 때의 답변에 대한 표절; 2) 제대로 소유하지 않고 내 대답을 수정하려고합니다. 3)이 사과는 너무 오래 걸렸습니다. 나는 처음에이 도전을 그냥 포기했을 정도로 절망했습니다. 약간의 배상 시도에서, 나는 r당신의 초기 근사치가 이미 상당히 좋기 때문에이 대답에 대한 나의 주요 개선이 뉴턴의 방법에서 증가로 바뀌 었다고 말하는 것이 공정하다고 생각합니다 . PPCG에서 다시 한 번 Hope기를 바랍니다. 모든 것에 대해 죄송합니다.
Sherlock9
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.