Котиться, щоб побачити всі сторони!


10

Скажімо, у вас 20-стороння штамповка. Ви починаєте котити, що гине, і вам доведеться прокатати її кілька десятків разів, перш ніж ви нарешті згорнете всі 20 значень. Вам цікаво, скільки рулонів мені потрібно, перш ніж я отримаю 50% шанс побачити всі 20 значень? І скільки рулонів стороннього nштампу мені потрібно закатати, перш ніж прокатати всі nсторони?

Після деяких досліджень ви дізнаєтесь, що існує формула для обчислення ймовірності прокрутки всіх nзначень після rрулонів.

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

де S(a, b)позначає номери Стірлінга другого роду , кількість способів розділити набір з n об’єктів (кожен рулон) на k непусті підмножини (кожна сторона).

Ви також знаходите послідовність OEIS , яку ми будемо називати R(n), що відповідає найменшій, rде P(r, n)принаймні 50%. Завдання полягає в тому, щоб обчислити nтретій термін цієї послідовності якомога швидше.

Змагання

  • З огляду на n, знайдіть найменше, r де P(r, n)більше або дорівнює 0.550%.
  • Ваш код теоретично повинен обробляти будь-яке невід’ємне ціле число nяк вхідне, але ми будемо тестувати ваш код лише в діапазоні 1 <= n <= 1000000.
  • Для озвучування, ми будемо приймати загальне час , необхідне для запуску R(n)на входах 1через 10000.
  • Ми будемо перевіряти , якщо ваші рішення правильні, запустивши нашу версію R(n)на свій вихід , щоб побачити , якщо P(your_output, n) >= 0.5і P(your_output - 1, n) < 0.5, то , що ваш вихід насправді найменший rдля даного n.
  • Ви можете використовувати будь-яке визначення S(a, b)у своєму рішенні. У Вікіпедії є кілька визначень, які тут можуть бути корисними.
  • Ви можете використовувати вбудовані рішення у своїх рішеннях, включаючи ті, що обчислюють S(a, b), або навіть ті, які обчислюють P(r, n)безпосередньо.
  • Ви можете жорстко кодувати до 1000 значень R(n)і мільйона чисел Стірлінга, хоча жодне з цих не є жорсткими межами, і їх можна змінити, якщо ви зможете зробити переконливий аргумент для їх підвищення або зниження.
  • Вам не потрібно перевіряти всі можливі rміж нами nта rми шукаємо, але вам потрібно знайти найменший, 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 Знаю, я мав обрати інше формулювання. Дякую за голову вгору
Шерлок9,

Відповіді:


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 , переставленої для чисельної стійкості та векторизації, щоб зробити метод Ньютона для пошуку r таким, що P ( r , n ) = 0,5, округлення r до цілого числа перед кожним кроком, поки крок не переміститься на r менше ніж на 3/4. З гарною початковою здогадкою, зазвичай це займає лише одну чи дві ітерації.

x i = log (1 - i / n ) = log (( n - i ) / n )
cx i = log ( n ! / (( n - i - 1)! ⋅ n i + 1 )
y i = cx i + cx n - i - 2 - cx n - 1 = log binom ( n , i + 1)
z i = (-1) i + 1 ⋅ біном ( n ,i + 1) ⋅ (( n - i - 1) / n ) r
1 + ∑ z i = n! ⋅ S ( r , n ) / n r = P ( r , n )
z ix i + 1 = (-1) i + 1 ⋅ біном ( n , i + 1) ⋅ (( n - i - 1) / n ) r log (( n - i - 1) / n)
z ix i + 1 = d / d r P ( r , n )


1
Відмінна робота над цілою відповіддю! По-перше, я мав усвідомити, що 0.366512це logщось із віків тому. Буде використаний -log(log(2)у моїй наступній ітерації. По-друге, ідея використовувати метод Ньютона також дуже розумна, і я радий бачити, що це працює так добре. По-третє, я майже напевно збираюся вкрасти exp(log(binom(n, i+1)) + r * log((n-i-1)/n)): P Kudos, чудова відповідь! : D
Шерлок9

1
Я додав офіційні терміни! Гарна відповідь BTW :)
Джеймс

2
Я справді розгублений. Я змінив numpyімпорт, from numpy import *і час чомусь впав до 0 ... Спробуйте в Інтернеті ?
notjagan

@notjagan кеш може потрапити?
NoOneIsHere

1
Я хочу вибачитися за кілька речей: 1) мій плагіат у вашій відповіді, коли я намагався знайти поліпшення; 2) не володіючи ним належним чином і просто намагаюся виправити свою відповідь; 3) що це вибачення зайняло так багато часу. Мене так надихнуло, що спочатку я просто відмовився від цього виклику. З невеликої спроби репарації я вважаю справедливим сказати вам, що моє основне вдосконалення щодо цієї відповіді було зміною від методу Ньютона до збільшення r, оскільки ваше початкове наближення вже досить добре. Сподіваюсь, що побачите вас в PPCG ще раз, і вибачте за все.
Шерлок9
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.