Эта задача частично является задачей алгоритмов, включает в себя некоторую математику и частично является самой быстрой задачей кода.
Для некоторого положительного целого числа n
рассмотрим равномерно случайную строку 1
s и 0
s длины n
и назовите ее A
. Теперь также рассмотрим вторую равномерно выбранную случайную строку длины n
, значения которой -1
, 0,
или, 1
и назовите ееB_pre
. Теперь позвольте B
быть B_pre
+ B_pre
. Это связано B_pre
с собой.
Теперь рассмотрим внутренний продукт A
и B[j,...,j+n-1]
и назовем его Z_j
и индекс из 1
.
задача
На выходе должен быть список n+1
фракций. У i
терма в выводе должна быть точная вероятность того, что все первые i
члены Z_j
с j <= i
равными 0
.
Гол
Самый большой, n
для которого ваш код дает правильный вывод менее чем за 10 минут на моей машине.
Tie Breaker
Если два ответа имеют одинаковую оценку, победит тот, который отправил первый.
В очень (очень) маловероятном случае, когда кто-то найдет способ получить неограниченное количество баллов, будет принято первое действительное доказательство такого решения.
намек
Не пытайтесь решить эту проблему математически, это слишком сложно. На мой взгляд, лучший способ - вернуться к базовым определениям вероятности из старшей школы и найти умные способы получить код, чтобы сделать исчерпывающий перечень возможностей.
Языки и библиотеки
Вы можете использовать любой язык со свободно доступным компилятором / интерпретатором и т. Д. для Linux и любых библиотек, которые также свободно доступны для Linux.
Моя машина Время будет работать на моей машине. Это стандартная установка Ubuntu на восьмиъядерный процессор AMD FX-8350. Это также означает, что мне нужно иметь возможность запустить ваш код. Как следствие, используйте только легкодоступное бесплатное программное обеспечение и, пожалуйста, включите подробные инструкции по компиляции и запуску вашего кода.
Некоторые тестовые выводы. Рассмотрим только первый вывод для каждого n
. Это когда i=1
. Для n
от 1 до 13 они должны быть.
1: 4/6
2: 18/36
3: 88/216
4: 454/1296
5: 2424/7776
6: 13236/46656
7: 73392/279936
8: 411462/1679616
9: 2325976/10077696
10: 13233628/60466176
11: 75682512/362797056
12: 434662684/2176782336
13: 2505229744/13060694016
Вы также можете найти общую формулу для i=1
на http://oeis.org/A081671 .
Таблица лидеров (разделена по языкам)
- n = 15. Python + параллельный питон + pypy за 1 мин 49 с Jakube
- n = 17. C ++ за 3 минуты 37 секунд Кит Рэндалл
- n = 16. C ++ за 2 минуты 38 с. kuroi neko
источник
Ответы:
C ++, n = 18 за 9 минут на 8 потоков
(Дайте мне знать, если на вашей машине это займет не более 10 минут.)
Я использую несколько форм симметрии в массиве B. Это циклический (сдвиг на одну позицию), реверс (обратный порядок элементов) и знак (взять отрицательный для каждого элемента). Сначала я вычисляю список Bs, которые нам нужно попробовать, и их вес. Затем каждый B проходит через быструю подпрограмму (с использованием инструкций подсчета битов) для всех 2 ^ n значений A.
Вот результат для n == 18:
Скомпилируйте программу ниже с
g++ --std=c++11 -O3 -mpopcnt dot.cc
источник
-pthread
снова вспомнить . Я добираюсь доn=17
своей машины.Python 2 с использованием pypy и pp: n = 15 за 3 минуты
Также просто грубая сила. Интересно видеть, что я почти получаю ту же скорость, что и курои нэко с C ++. Мой код может достигать
n = 12
около 5 минут. И я запускаю его только на одном виртуальном ядре.изменить: уменьшить пространство поиска в
n
Я заметил, что циклическое вектор
A*
изA
производит одни и то же число , как и вероятности ( то же номера) в качестве исходного вектора ,A
когда я перебиратьB
. Например , вектор(1, 1, 0, 1, 0, 0)
имеет ту же вероятность , как каждый из векторов(1, 0, 1, 0, 0, 1)
,(0, 1, 0, 0, 1, 1)
,(1, 0, 0, 1, 1, 0)
,(0, 0, 1, 1, 0, 1)
и(0, 1, 1, 0, 1, 0)
при выборе случайного образомB
. Поэтому мне не нужно перебирать каждый из этих 6 векторов, а только около 1 и заменятьcount[i] += 1
наcount[i] += cycle_number
.Это уменьшает сложность от
Theta(n) = 6^n
доTheta(n) = 6^n / n
. Поэтомуn = 13
это примерно в 13 раз быстрее, чем моя предыдущая версия. Он рассчитываетсяn = 13
примерно за 2 минуты 20 секунд. Потомуn = 14
что это все еще слишком медленно. Это займет около 13 минут.редактировать 2: многоядерное программирование
Не очень доволен следующим улучшением. Я решил также попытаться выполнить мою программу на нескольких ядрах. На моих 2 + 2 ядрах я теперь могу вычислить
n = 14
примерно за 7 минут. Только улучшение в 2 раза.Код доступен в этом репозитории github: Ссылка . Многоядерное программирование делает его немного уродливым.
редактировать 3: Сокращение пространства поиска для
A
векторов иB
векторовЯ заметил ту же зеркальную симметрию для векторов,
A
что и Курой Неко. Все еще не уверен, почему это работает (и если это работает для каждогоn
).Сокращение пространства поиска для
B
векторов немного умнее. Я заменил генерацию векторов (itertools.product
) собственной функцией. По сути, я начинаю с пустого списка и помещаю его в стек. Пока стек не пуст, я удаляю список, если он не имеет той же длины, чтоn
и я, я генерирую 3 других списка (добавляя -1, 0, 1) и помещая их в стек. У меня такой же список, какn
и у меня, я могу оценить суммы.Теперь, когда я сам генерирую векторы, я могу фильтровать их в зависимости от того, смогу ли я достичь суммы = 0 или нет. Например, если мой вектор
A
есть(1, 1, 1, 0, 0)
, и мой векторB
выглядит(1, 1, ?, ?, ?)
, я знаю, что я не могу заполнить?
значениями, так чтоA*B = 0
. Поэтому мне не нужно перебирать все эти 6 векторовB
формы(1, 1, ?, ?, ?)
.Мы можем улучшить это, если проигнорируем значения для 1. Как отмечалось в вопросе, значения для
i = 1
представляют собой последовательность A081671 . Есть много способов рассчитать их. Я выбираю простое повторениеa(n) = (4*(2*n-1)*a(n-1) - 12*(n-1)*a(n-2)) / n
. Так как мы можем вычислитьi = 1
практически за короткое время, мы можем отфильтровать больше векторовB
. НапримерA = (0, 1, 0, 1, 1)
иB = (1, -1, ?, ?, ?)
. Мы можем игнорировать векторы, где первый? = 1
, потому чтоA * cycled(B) > 0
, для всех этих векторов. Я надеюсь, что вы можете следовать. Это, наверное, не лучший пример.С этим я могу рассчитать
n = 15
за 6 минут.редактировать 4:
Быстро реализовал отличную идею Куроя Неко, которая гласит, что
B
и-B
дает те же результаты. Ускорение х2. Реализация - это только быстрый взлом.n = 15
через 3 минуты.Код:
Для полного кода посетите Github . Следующий код является только представлением основных функций. Я пропустил импорт, многоядерное программирование, печать результатов, ...
Использование:
Вы должны установить Pypy (для Python 2 !!!). Модуль параллельного Python не портирован для Python 3. Затем вам необходимо установить модуль параллельного Python pp-1.6.4.zip . Распакуйте его
cd
в папку и позвонитеpypy setup.py install
.Тогда вы можете вызвать мою программу с
pypy you-do-the-math.py 15
Он автоматически определит количество процессоров. После завершения программы могут быть сообщения об ошибках, просто игнорируйте их.
n = 16
должно быть возможно на вашей машине.Выход:
Заметки и идеи:
A & B
и сосчитать блоки 01 и 10. Циклирование может быть выполнено с помощью смещения вектора и использования масок ... Я на самом деле все это реализовал, вы можете найти это в некоторых моих старых коммитах на Github. Но оказалось, что медленнее, чем со списками. Я думаю, Pypy действительно оптимизирует операции со списком.источник
неуклюжий хулиган - C ++ - слишком медленно
Ну, так как лучший программист взял реализацию C ++, я призываю к выходу для этого.
Сборка исполняемого файла
Это автономный источник C ++ 11, который компилируется без предупреждений и работает на:
Если вы компилируете с g ++, используйте: g ++ -O3 -pthread -std = c ++ 11, если вы забудете
об этом, вы
-pthread
получите приятный и удобный дамп ядра.Оптимизации
Последний член Z равен первому (Bpre x A в обоих случаях), поэтому последние два результата всегда равны, что позволяет вычислить последнее значение Z.
Усиление ничтожно мало, но его кодирование ничего не стоит, поэтому вы можете использовать его.
Как обнаружил Якуб, все циклические значения данного вектора A дают одинаковые вероятности.
Вы можете вычислить их с одним экземпляром A и умножить результат на количество его возможных поворотов. Группы вращения легко могут быть предварительно вычислены за незначительное количество времени, так что это огромный прирост скорости.
Поскольку число перестановок вектора длины n равно n-1, сложность падает с o (6 n ) до o (6 n / (n-1)), что в основном делает шаг вперед в течение того же времени вычисления.
Похоже, пары симметричных паттернов также генерируют одинаковые вероятности. Например, 100101 и 101001.
У меня нет математических доказательств этого, но интуитивно, когда представлены все возможные шаблоны B, каждое симметричное значение A будет свернуто с соответствующим симметричным значением B для того же глобального результата.
Это позволяет перегруппировать еще несколько векторов A, чтобы уменьшить число групп A примерно на 30%.
НЕПРАВИЛЬНО
По какой-то полузадачной причине все шаблоны с одним или двумя установленными битами дают одинаковый результат. Это не означает, что многие отдельные группы, но все же они могут быть объединены практически бесплатно.Векторы B и -B (B со всеми компонентами, умноженными на -1) дают одинаковые вероятности.
(например, [1, 0, -1, 1] и [-1, 0, 1, -1]).
За исключением нулевого вектора (все компоненты равны 0), B и -B образуют пару различных векторов.
Это позволяет сократить число значений B пополам, учитывая только одно из каждой пары и умножая его вклад на 2, добавляя известный глобальный вклад нулевого B к каждой вероятности только один раз.
Как это устроено
Количество значений B огромно (3 n ), поэтому для их предварительного вычисления потребуются неприличные объемы памяти, что замедлит вычисления и в конечном итоге исчерпает доступную оперативную память.
К сожалению, я не смог найти простой способ перечисления половины набора оптимизированных значений B, поэтому я прибег к кодированию выделенного генератора.
Могучий генератор B был очень забавным для кода, хотя языки, которые поддерживают механизмы выхода, позволили бы программировать его намного более элегантным способом.
Вкратце, он рассматривает «скелет» вектора Bpre как двоичный вектор, где 1 представляют действительные значения -1 или +1.
Среди всех этих значений потенциала + 1 / -1 первое фиксируется на +1 (таким образом выбирается один из возможных векторов B / -B), и перечисляются все остальные возможные комбинации + 1 / -1.
Наконец, простая система калибровки гарантирует, что каждый рабочий поток будет обрабатывать диапазон значений примерно одинакового размера.
Значения сильно фильтруются для перегруппировки в равновероятных кусках.
Это делается на этапе предварительного вычисления, при котором перебор проверяет все возможные значения.
Эта часть имеет незначительное время выполнения O (2 n ) и не нуждается в оптимизации (код уже достаточно нечитаемый, как есть!).
Чтобы оценить внутренний продукт (который нужно проверить только на ноль), компоненты -1 и 1 группы B перегруппированы в двоичные векторы.
Внутренний продукт равен нулю, если (и только если) имеется равное число + 1 с и -1 с среди значений B, соответствующих ненулевым значениям A.
Это можно вычислить с помощью простых операций маскирования и подсчета битов, с помощью
std::bitset
которых можно получить достаточно эффективный код подсчета бит без необходимости прибегать к уродливым внутренним инструкциям.Работа распределяется поровну между ядрами, с принудительной привязкой к процессору (каждый немного помогает, или, как они говорят).
Пример результата
Выступления
Многопоточность должна отлично работать на этом, хотя только «истинные» ядра будут полностью способствовать скорости вычислений. Мой процессор имеет только 2 ядра для 4 процессоров, и выигрыш по сравнению с однопоточной версией составляет «только» около 3,5.
Составители
Первоначальная проблема с многопоточностью привела меня к мысли, что компиляторы GNU работают хуже, чем Microsoft.
После более тщательного тестирования, похоже, g ++ снова побеждает, производя код примерно на 30% быстрее (такое же соотношение я заметил и в двух других проектах, требующих большого объема вычислений).
Примечательно, что
std::bitset
библиотека реализована с помощью специальных инструкций подсчета битов в g ++ 4.8, в то время как MSVC 2013 использует только циклы обычных битовых сдвигов.Как и следовало ожидать, компиляция в 32 или 64 битах не имеет значения.
Дальнейшие уточнения
Я заметил, что несколько групп А производят одинаковые вероятности после всех операций сокращения, но я не смог определить схему, которая позволила бы их перегруппировать.
Вот пары, которые я заметил для n = 11:
источник
terminate called after throwing an instance of 'std::system_error' what(): Unknown error -1 Aborted (core dumped)