[Это вопрос партнера, чтобы точно рассчитать вероятность ]
Эта задача о написании кода для точного и быстрого вычисления вероятности . Вывод должен быть точной вероятностью, записанной в виде дроби в наиболее сокращенной форме. То есть это никогда не должно выводиться, 4/8
а скорее 1/2
.
Для некоторого положительного целого числа n
рассмотрим равномерно случайную строку длиной 1 с и 1 с n
и назовем ее A. Теперь объединяем A
ее первое значение. То есть A[1] = A[n+1]
если индексирование от 1. A
теперь имеет длину n+1
. Теперь также рассмотрим вторую случайную строку длины n
, первые n
значения которой равны -1, 0 или 1 с вероятностью 1 / 4,1 / 2, 1/4 каждая и назовем ее B.
Теперь рассмотрим внутреннее произведение A[1,...,n]
и B
и и внутреннее произведение A[2,...,n+1]
и B
.
Например, рассмотрим n=3
. Возможные значения A
и B
могут быть A = [-1,1,1,-1]
и B=[0,1,-1]
. В этом случае два внутренних продукта 0
и 2
.
Ваш код должен выводить вероятность того, что оба внутренних продукта равны нулю.
Копируя таблицу, созданную Мартином Бюттнером, мы получаем следующий пример результатов.
n P(n)
1 1/2
2 3/8
3 7/32
4 89/512
5 269/2048
6 903/8192
7 3035/32768
8 169801/2097152
Языки и библиотеки
Вы можете использовать любой свободно доступный язык и библиотеки, которые вам нравятся. Я должен быть в состоянии запустить ваш код, поэтому, пожалуйста, включите полное объяснение того, как запустить / скомпилировать ваш код в Linux, если это вообще возможно.
Задание
Ваш код должен начинаться с n=1
и давать правильный вывод для каждого увеличивающегося n в отдельной строке. Он должен остановиться через 10 секунд.
Счет
Это просто самый высокий балл n
до того, как ваш код останавливается через 10 секунд при запуске на моем компьютере. Если есть ничья, победителем будет тот, кто быстрее наберет наибольшее количество очков.
Таблица записей
n = 64
в Python . Версия 1 Митча Шварцаn = 106
в Python . Версия 11 июня 2015 года от Митча Шварцаn = 151
в C ++ . Порт Митча Шварца ответ kirbyfan64sosn = 165
в Python . Версия 11 июня 2015 года "обрезка" версия Митча Шварца сN_MAX = 165
.n = 945
в Python Min_25 с использованием точной формулы. Удивительно!n = 1228
в Python Митч Шварц, используя другую точную формулу (на основе предыдущего ответа Min_25).n = 2761
в Python Митч Шварц, используя более быструю реализацию той же точной формулы.n = 3250
в Python с использованием Pypy Митч Шварц, используя ту же реализацию. Этот счет долженpypy MitchSchwartz-faster.py |tail
избежать прокрутки консоли.
источник
Ответы:
питон
Замкнутая форма формула
p(n)
ISЭкспоненциальная производящая функция
p(n)
ISгде
I_0(x)
модифицированная функция Бесселя первого рода.Изменить на 2015-06-11:
- обновлен код Python.
Изменить на 2015-06-13:
- добавлено доказательство вышеуказанной формулы.
- исправил
time_limit
.- добавил код PARI / GP.
питон
PARI / GP
Доказательство:
эта проблема аналогична двумерной (ограниченной) задаче случайного блуждания.
Если
A[i] = A[i+1]
мы можем перейти от(x, y)
к(x+1, y+1)
[1 путь],(x, y)
[2 способа] или(x-1, y-1)
[1] способ.Если
A[i] != A[i+1]
мы можем перейти от(x, y)
к(x-1, y+1)
[1 путь],(x, y)
[2 способа] или(x+1, y-1)
[1] способ.Пусть
a(n, m) = [x^m]((x+1)^n + (x-1)^n)
,b(n) = [x^n](1+x)^{2n}
иc(n)
быть число способов , чтобы перейти от(0, 0)
к(0, 0)
сn
шагами.Затем,
c(n) = \sum_{i=0}^n a(n, i) * b(i) * b(n-i).
Так как
p(n) = c(n) / 8^n
мы можем получить приведенную выше формулу замкнутой формы.источник
питон
Примечание: Поздравляем Min_25 с поиском решения в закрытой форме!
Спасибо за интересную проблему! Это можно решить с помощью DP, хотя в настоящее время я не чувствую особой мотивации оптимизировать скорость, чтобы получить более высокий балл. Это может быть хорошо для гольфа.
Код достиг
N=39
10 секунд на этом старом ноутбуке с Python 2.7.5.Для кортежа
(a,b,s,t)
:a
является первым элементомA
,b
является последним элементомB
,s
является внутренним произведениемA[:-1]
иB
, иt
является внутренним произведениемA[1:-1]
иB[:-1]
, используя нотацию фрагмента Python. Мой код не хранит массивыA
илиB
где-либо еще, поэтому я использую эти буквы для обозначения следующих элементов, которые будут добавлены,A
иB
, соответственно. Этот выбор именования переменных делает объяснение немного неловким, но позволяет хорошо выглядетьA*b+a*B
в самом коде. Обратите внимание, что добавляемый элементA
является предпоследним, поскольку последний элемент всегда совпадает с первым. Я использовал трюк Мартина Бюттнера, в том числе0
дваждыB
кандидатов для того, чтобы получить правильное распределение вероятностей. СловарьX
(который названY
дляN+1
) отслеживает количество всех возможных массивов в соответствии со значением кортежа. Переменныеn
иd
обозначают числитель и знаменатель, поэтому я переименовалn
задачу в «N
.Ключевая часть логики в том , что вы можете обновить от
N
доN+1
использования только значения в кортеже. Два внутренних продукта, указанные в вопросе, заданыs+A*B
иt+A*b+a*B
. Это ясно, если вы немного изучите определения; Обратите внимание, что[A,a]
и[b,B]
последние два элемента массивовA
иB
соответственно.Обратите внимание, что
s
иt
являются небольшими и ограниченными в соответствииN
, и для быстрой реализации на быстром языке мы могли бы избегать словарей в пользу массивов.Можно использовать симметрию, учитывая значения, отличающиеся только знаком; Я не смотрел на это.
Замечание 1 : Размер словаря увеличивается в квадрате
N
, где размер означает количество пар ключ-значение.Замечание 2 : Если мы устанавливаем верхнюю границу
N
, то мы можем сократить кортежи, для которыхN_MAX - N <= |s|
и аналогично дляt
. Это можно сделать, указав поглощающее состояние, или неявно с помощью переменной, которая будет содержать количество сокращенных состояний (которое нужно будет умножать на 8 на каждой итерации).Обновление : эта версия быстрее:
Проведены оптимизации:
main()
- доступ к локальной переменной быстрее, чем глобальныйN=1
явно, чтобы избежать проверки(1,-1) if N else [a]
(которая обеспечивает соответствие первого элемента в кортеже при добавлении элементов кA
запуску из пустого списка)c
для добавления0
кB
вместо выполнения этих операций дважды8^N
поэтому нам не нужно следить за нимA
as1
и разделить знаменатель на2
, так как действительные пары(A,B)
сA[1]=1
и сA[1]=-1
могут быть приведены в однозначное соответствие путем отрицанияA
. Точно так же мы можем исправить первый элементB
как неотрицательный.N_MAX
чтобы узнать, сколько очков он может получить на вашей машине. Это может быть переписано, чтобы найти подходящийN_MAX
автоматически с помощью бинарного поиска, но кажется ненужным? Примечание. Нам не нужно проверять условия сокращения до тех пор, пока не достигнем нужного уровняN_MAX / 2
, поэтому мы могли бы немного ускорить процесс, выполняя итерации в два этапа, но я решил не делать этого для простоты и чистоты кода.источник
N=57
для первой версии иN=75
для второй.питон
Используя идею Min_25 о случайном блуждании, я смог прийти к другой формуле:
Вот реализация Python, основанная на Min_25:
Объяснение / доказательство:
Сначала мы решаем связанную проблему подсчета, где мы разрешаем
A[n+1] = -A[1]
; то есть дополнительный элемент, связанный с,A
может быть1
или-1
независимо от первого элемента. Поэтому нам не нужно отслеживать, сколько раз этоA[i] = A[i+1]
происходит. У нас есть следующая случайная прогулка:От
(x,y)
мы можем перейти к(x+1,y+1)
[1 путь],(x+1,y-1)
[1 путь],(x-1,y+1)
[1 путь],(x-1,y-1)
[1 путь],(x,y)
[4 пути]где
x
иy
стенд для двух продуктов точечных, и мы рассчитываем количество способов , чтобы перейти от(0,0)
к(0,0)
вn
шагах. Затем этот счет будет умножен на,2
чтобы учесть тот факт, чтоA
можно начинать с1
или-1
.Мы имеем в виду пребывание в
(x,y)
качестве нулевого шага .Мы перебираем количество ненулевых ходов
i
, которое должно быть четным, чтобы вернуться к(0,0)
. Горизонтальные и вертикальные движения составляют два независимых одномерных случайных блуждания, которые можно посчитатьC(i,i/2)^2
, гдеC(n,k)
биномиальный коэффициент. (Для обхода сk
шагами влево иk
шагами вправо естьC(2k,k)
способы выбрать порядок шагов.) Кроме того, существуютC(n,i)
способы размещения ходов и4^(n-i)
способы выбора нулевых ходов. Итак, мы получаем:Теперь нам нужно вернуться к исходной проблеме. Определите допустимую пару
(A,B)
для конвертации, если онаB
содержит ноль. Определите пару,(A,B)
которая будет почти допустимой, еслиA[n+1] = -A[1]
оба продукта с точками равны нулю.Лемма. Для данного
n
случая почти допустимые пары находятся во взаимно однозначном соответствии с конвертируемыми парами.Мы можем (обратимо) преобразовать конвертируемую пару
(A,B)
в почти допустимую пару(A',B')
, отрицаяA[m+1:]
иB[m+1:]
, гдеm
индекс последнего нуля вB
. Проверка для этого проста: если последний элементB
равен нулю, нам не нужно ничего делать. В противном случае, когда мы отменяем последний элементA
, мы можем отрицать последний элементB
, чтобы сохранить последний член смещенного точечного произведения. Но это сводит на нет последнее значение неперемещенного точечного произведения, поэтому мы исправляем это, нейтрализуя элемент предпоследнего элементаA
. Но тогда это сбрасывает значение второго с последним смещенного произведения, поэтому мы отрицаем элемент с последним до последнегоB
. И так далее, до достижения нулевого элемента вB
.Теперь нам просто нужно показать, что нет практически допустимых пар, для которых
B
не содержится ноль. Чтобы скалярное произведение равнялось нулю, мы должны иметь равное число1
и-1
условия для исключения. Каждый-1
термин состоит из(1,-1)
или(-1,1)
. Таким образом, соотношение числа-1
происходящих событий устанавливается согласноn
. Если первый и последний элементыA
имеют разные знаки, мы меняем соотношение, поэтому это невозможно.Итак, мы получаем
что дает приведенную выше формулу (переиндексация с
i' = i/2
).Обновление: вот более быстрая версия с той же формулой:
Проведены оптимизации:
p(n)
C(n,k)
сk <= n/2
источник
p(n)
не нужно быть кусочной функцией. В общем, еслиf(n) == {g(n) : n is odd; h(n) : n is even}
тогда вы можете написатьf(n) == (n-2*floor(n/2))*g(n) + ((n+1)-2*(floor((n+1)/2)))*h(n)
или использоватьn mod 2
вместо(n-2*floor(n/2))
. Смотрите здесьЭкспликация формулы Min_25
Min_25 опубликовал отличное доказательство, но это заняло некоторое время. Это небольшое объяснение, чтобы заполнить между строк.
a (n, m) представляет количество способов выбора A, таких что A [i] = A [i + 1] m раз. Формула для a (n, m) эквивалентна a (n, m) = {2 * (n выберите m) для четного nm; 0 для нм нечетное.} Допускается только одна четность, потому что A [i]! = A [i + 1] должно происходить четное число раз, чтобы A [0] = A [n]. Коэффициент 2 обусловлен первоначальным выбором A [0] = 1 или A [0] = -1.
Как только число (A [i]! = A [i + 1]) установлено равным q (названному i в формуле c (n)), оно разделяется на два одномерных случайных блуждания длины q и nq. b (m) - это количество способов совершить одномерный случайный обход m шагов, который заканчивается в том же месте, в котором он был начат, и имеет 25% шансов двигаться влево, 50% шансов остаться на месте и 25% шансов двигаясь вправо. Более очевидный способ сформулировать производящую функцию - это [x ^ m] (1 + 2x + x ^ 2) ^ n, где 1, 2x и x ^ 2 обозначают слева, без движения и вправо соответственно. Но тогда 1 + 2x + x ^ 2 = (x + 1) ^ 2.
источник
C ++
Просто порт (превосходного) ответа Питона Митча Шварца. Основное отличие заключается в том , что я использовал
2
для представления-1
дляa
переменного и сделал что - то похожее наb
, что позволило мне использовать массив. Используя Intel C ++ с-O3
, я получилN=141
! Моя первая версия досталасьN=140
.Это использует Boost. Я попробовал параллельную версию, но столкнулся с некоторыми проблемами.
источник
g++ -O3 kirbyfan64sos.cpp -o kirbyfan64sos -lboost_system -lboost_timer -lboost_chrono -lrt -lgmp
скомпилировать. (Спасибо aditsu.)