Python 2, використовуючи pypy і pp: n = 15 за 3 хвилини
Також просто проста груба сила. Цікаво побачити, що я майже отримую ту саму швидкість, що і kuroi neko з 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 repo: Посилання . Багатоядерне програмування робить трохи некрасивим.
редагувати 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:
Швидко реалізувати Kuroi Неко відмінну ідею, яка говорить, що Bі -Bробить ті ж самі результати. Швидкість x2. Однак впровадження - це лише швидкий злом. n = 15за 3 хвилини.
Код:
Щоб отримати повний код, відвідайте Github . Наступний код є лише поданням основних особливостей. Я залишив імпорт, багатоядерне програмування, друк результатів, ...
count = [0] * n
count[0] = oeis_A081671(n)
#generating all important vector A
visited = set(); todo = dict()
for A in product((0, 1), repeat=n):
if A not in visited:
# generate all vectors, which have the same probability
# mirrored and cycled vectors
same_probability_set = set()
for i in range(n):
tmp = [A[(i+j) % n] for j in range(n)]
same_probability_set.add(tuple(tmp))
same_probability_set.add(tuple(tmp[::-1]))
visited.update(same_probability_set)
todo[A] = len(same_probability_set)
# for each vector A, create all possible vectors B
stack = []
for A, cycled_count in dict_A.iteritems():
ones = [sum(A[i:]) for i in range(n)] + [0]
# + [0], so that later ones[n] doesn't throw a exception
stack.append(([0] * n, 0, 0, 0, False))
while stack:
B, index, sum1, sum2, used_negative = stack.pop()
if index < n:
# fill vector B[index] in all possible ways,
# so that it's still possible to reach 0.
if used_negative:
for v in (-1, 0, 1):
sum1_new = sum1 + v * A[index]
sum2_new = sum2 + v * A[index - 1 if index else n - 1]
if abs(sum1_new) <= ones[index+1]:
if abs(sum2_new) <= ones[index] - A[n-1]:
C = B[:]
C[index] = v
stack.append((C, index + 1, sum1_new, sum2_new, True))
else:
for v in (0, 1):
sum1_new = sum1 + v * A[index]
sum2_new = sum2 + v * A[index - 1 if index else n - 1]
if abs(sum1_new) <= ones[index+1]:
if abs(sum2_new) <= ones[index] - A[n-1]:
C = B[:]
C[index] = v
stack.append((C, index + 1, sum1_new, sum2_new, v == 1))
else:
# B is complete, calculate the sums
count[1] += cycled_count # we know that the sum = 0 for i = 1
for i in range(2, n):
sum_prod = 0
for j in range(n-i):
sum_prod += A[j] * B[i+j]
for j in range(i):
sum_prod += A[n-i+j] * B[j]
if sum_prod:
break
else:
if used_negative:
count[i] += 2*cycled_count
else:
count[i] += cycled_count
Використання:
Ви повинні встановити 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має бути можливим на вашій машині.
Вихід:
Calculation for n = 15 took 2:50 minutes
1 83940771168 / 470184984576 17.85%
2 17379109692 / 470184984576 3.70%
3 3805906050 / 470184984576 0.81%
4 887959110 / 470184984576 0.19%
5 223260870 / 470184984576 0.05%
6 67664580 / 470184984576 0.01%
7 30019950 / 470184984576 0.01%
8 20720730 / 470184984576 0.00%
9 18352740 / 470184984576 0.00%
10 17730480 / 470184984576 0.00%
11 17566920 / 470184984576 0.00%
12 17521470 / 470184984576 0.00%
13 17510280 / 470184984576 0.00%
14 17507100 / 470184984576 0.00%
15 17506680 / 470184984576 0.00%
Примітки та ідеї:
- У мене процесор i7-4600m з 2 ядрами і 4 потоками. Не має значення, якщо я використовую 2 або 4 нитки. Використання процесора становить 50% з 2 потоками і 100% з 4 потоками, але це все одно займає стільки ж часу. Я не знаю чому. Я перевірив, що кожен потік містить лише половину даних, коли є 4 потоки, перевірив результати, ...
- Я використовую безліч списків. Python не дуже ефективний для зберігання, мені доведеться копіювати багато списків, ... Тому я подумав використовувати ціле число. Я міг би використовувати біти 00 (для 0) та 11 (для 1) у векторі A, а біти 10 (для -1), 00 (для 0) та 01 (для 1) у векторі B. Для продукту з A і B, я повинен був би лише обчислити
A & Bі порахувати блоки 01 і 10. Велоспорт можна зробити зі зміщенням вектора та використанням масок, ... Я фактично все це реалізував, ви можете знайти його в деяких моїх старих комісіях на Github. Але виявилося повільніше, ніж зі списками. Я думаю, pypy дійсно оптимізує операції зі списком.