Python 2 sử dụng pypy và pp: n = 15 trong 3 phút
Cũng chỉ là một lực lượng vũ phu đơn giản. Thật thú vị khi thấy rằng tôi gần như có được tốc độ tương tự như Kuroi neko với C ++. Mã của tôi có thể đạt được n = 12
trong khoảng 5 phút. Và tôi chỉ chạy nó trên một lõi ảo.
chỉnh sửa: Giảm không gian tìm kiếm theo hệ số n
Tôi nhận thấy, đó là một vector đạp xe A*
của A
sản xuất những con số tương tự như xác suất (số giống nhau) là vectơ ban đầu A
khi tôi lặp qua B
. Ví dụ: Các vector (1, 1, 0, 1, 0, 0)
có các xác suất tương tự như mỗi người trong số các vectơ (1, 0, 1, 0, 0, 1)
, (0, 1, 0, 0, 1, 1)
, (1, 0, 0, 1, 1, 0)
, (0, 0, 1, 1, 0, 1)
và (0, 1, 1, 0, 1, 0)
khi lựa chọn ngẫu nhiên B
. Do đó, tôi không phải lặp đi lặp lại trong mỗi 6 vectơ này, nhưng chỉ có khoảng 1 và thay thế count[i] += 1
bằng count[i] += cycle_number
.
Điều này làm giảm sự phức tạp từ Theta(n) = 6^n
đến Theta(n) = 6^n / n
. Do đó, n = 13
nó nhanh gấp khoảng 13 lần so với phiên bản trước của tôi. Nó tính toán n = 13
trong khoảng 2 phút 20 giây. Đối với n = 14
nó vẫn còn một chút quá chậm. Mất khoảng 13 phút.
chỉnh sửa 2: Lập trình đa lõi
Không thực sự hài lòng với sự cải tiến tiếp theo. Tôi quyết định cũng cố gắng thực hiện chương trình của mình trên nhiều lõi. Trên lõi 2 + 2 của tôi bây giờ tôi có thể tính toán n = 14
trong khoảng 7 phút. Chỉ có một yếu tố của 2 cải thiện.
Mã có sẵn trong repo github này: Liên kết . Các chương trình đa lõi làm cho một chút xấu xí.
chỉnh sửa 3: Giảm không gian tìm kiếm cho A
vectơ và B
vectơ
Tôi nhận thấy sự đối xứng gương tương tự cho các vectơ A
như Kuroi neko đã làm. Vẫn không chắc chắn, tại sao điều này hoạt động (và nếu nó hoạt động cho mỗin
).
Việc giảm không gian tìm kiếm cho B
vectơ thông minh hơn một chút. Tôi đã thay thế thế hệ của vectơ ( itertools.product
), bằng một hàm riêng. Về cơ bản, tôi bắt đầu với một danh sách trống và đặt nó vào một ngăn xếp. Cho đến khi ngăn xếp trống, tôi xóa một danh sách, nếu nó không có cùng chiều dài n
, tôi tạo 3 danh sách khác (bằng cách nối thêm -1, 0, 1) và đẩy chúng vào ngăn xếp. Tôi một danh sách có cùng độ dài n
, tôi có thể đánh giá các khoản tiền.
Bây giờ tôi tự tạo các vectơ, tôi có thể lọc chúng tùy thuộc vào việc tôi có thể đạt tổng = 0 hay không. Ví dụ, nếu vectơ của tôi A
là (1, 1, 1, 0, 0)
, và vectơ của tôi B
trông (1, 1, ?, ?, ?)
, tôi biết, rằng tôi không thể điền vào các ?
giá trị, vì vậy A*B = 0
. Vì vậy, tôi không phải lặp đi lặp lại trên tất cả 6 vectơ B
của biểu mẫu (1, 1, ?, ?, ?)
.
Chúng ta có thể cải thiện điều này, nếu chúng ta bỏ qua các giá trị cho 1. Như đã lưu ý trong câu hỏi, đối với các giá trị i = 1
là chuỗi A081671 . Có nhiều cách để tính toán chúng. Tôi chọn tái phát đơn giản : a(n) = (4*(2*n-1)*a(n-1) - 12*(n-1)*a(n-2)) / n
. Vì i = 1
về cơ bản chúng ta có thể tính toán không có thời gian, chúng ta có thể lọc nhiều vectơ hơn B
. Ví dụ A = (0, 1, 0, 1, 1)
và B = (1, -1, ?, ?, ?)
. Chúng ta có thể bỏ qua các vectơ, trong đó đầu tiên ? = 1
, bởi vì A * cycled(B) > 0
, cho tất cả các vectơ này. Tôi hy vọng bạn có thể làm theo. Đây có lẽ không phải là ví dụ tốt nhất.
Với điều này tôi có thể tính toán n = 15
trong 6 phút.
chỉnh sửa 4:
Nhanh chóng thực hiện ý tưởng tuyệt vời của Kuroi neko, trong đó nói rằng, B
và -B
tạo ra kết quả tương tự. Tăng tốc x2. Thực hiện chỉ là một hack nhanh chóng, mặc dù. n = 15
trong 3 phút
Mã số:
Đối với mã hoàn chỉnh, hãy truy cập Github . Các mã sau đây chỉ là một đại diện của các tính năng chính. Tôi đã bỏ nhập khẩu, lập trình đa lõi, in kết quả, ...
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
Sử dụng:
Bạn phải cài đặt pypy (cho Python 2 !!!). Mô-đun python song song không được chuyển cho Python 3. Sau đó, bạn phải cài đặt mô-đun python song song pp-1.6.4.zip . Giải nén nó, cd
vào thư mục và gọi pypy setup.py install
.
Sau đó, bạn có thể gọi chương trình của tôi với
pypy you-do-the-math.py 15
Nó sẽ tự động xác định số lượng cpu. Có thể có một số thông báo lỗi sau khi kết thúc chương trình, chỉ cần bỏ qua chúng. n = 16
nên có thể trên máy của bạn.
Đầu ra:
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%
Ghi chú và ý tưởng:
- Tôi có bộ xử lý i7-4600m với 2 lõi và 4 luồng. Nó không thành vấn đề nếu tôi sử dụng 2 hoặc 4 chủ đề. Việc sử dụng cpu là 50% với 2 luồng và 100% với 4 luồng, nhưng vẫn mất cùng một khoảng thời gian. Tôi không biết tại sao. Tôi đã kiểm tra, rằng mỗi luồng chỉ có một nửa dữ liệu, khi có 4 luồng, kiểm tra kết quả, ...
- Tôi sử dụng rất nhiều danh sách. Python không hiệu quả trong việc lưu trữ, tôi phải sao chép nhiều danh sách, ... Vì vậy, tôi đã nghĩ đến việc sử dụng một số nguyên thay thế. Tôi có thể sử dụng các bit 00 (cho 0) và 11 (cho 1) trong vectơ A và các bit 10 (cho -1), 00 (cho 0) và 01 (cho 1) trong vectơ B. Đối với sản phẩm của A và B, tôi sẽ chỉ phải tính
A & B
và đếm các khối 01 và 10. Đi xe đạp có thể được thực hiện với việc dịch chuyển vectơ và sử dụng mặt nạ, ... Tôi thực sự đã thực hiện tất cả điều này, bạn có thể tìm thấy nó trong một số cam kết cũ của tôi trên Github. Nhưng hóa ra, để chậm hơn so với danh sách. Tôi đoán, pypy thực sự tối ưu hóa danh sách hoạt động.