使用pypy和pp的Python 2:3分钟内n = 15
也只是简单的蛮力。有趣的是,我使用C ++获得的速度几乎与kuroi neko相同。我的代码可以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存储库中可用:Link。多核程序设计有点丑陋。
编辑3:减少A
向量和B
向量的搜索空间
我注意到与A
kuroi neko一样,向量具有相同的镜像对称性。仍然不确定为什么这样做(以及是否适用于每种方法n
)。
减少B
向量的搜索空间会比较聪明。我用itertools.product
自己的函数替换了向量()的生成。基本上,我从一个空列表开始,然后将其放在堆栈上。直到堆栈为空,我删除了一个列表,如果列表的长度与相同n
,则会生成3个其他列表(通过附加-1、0、1)并将其推入堆栈。如果列表的长度与相同n
,则我可以求和。
现在,我自己生成了向量,可以根据是否可以达到sum = 0来对其进行过滤。例如,如果我的向量A
为(1, 1, 1, 0, 0)
,并且我的向量B
看起来像是(1, 1, ?, ?, ?)
我无法?
用值填充,那么A*B = 0
。因此,我不必遍历B
形式的所有这6个向量(1, 1, ?, ?, ?)
。
如果忽略1的值,则可以对此进行改进。如问题中所指出的,因为A的值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 neko的好主意,即说,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模块未移植到Python3。然后,您必须安装并行python模块pp-1.6.4.zip。将其解压缩cd
到该文件夹中,然后调用pypy setup.py install
。
然后您可以使用
pypy you-do-the-math.py 15
它将自动确定cpu的数量。程序完成后可能会出现一些错误消息,请忽略它们。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%
注意事项和想法:
- 我有一个i2-4600m处理器,带有2个核心和4个线程。我使用2个线程还是4个线程都没有关系。2个线程的cpu使用率是50%,4个线程的cpu使用率是100%,但是仍然需要相同的时间。我不知道为什么 我检查了一下,每个线程只有一半的数据,当有4个线程时,检查了结果,...
- 我使用很多清单。Python的存储效率不是很高,我必须复制很多列表,所以我想到了使用整数代替。我可以在向量A中使用位00(对于0)和11(对于1),以及向量B中的位10(对于-1),00(对于0)和01(对于1)。对于乘积对于A和B,我只需要计算
A & B
和计算01和10块。循环可以通过移动向量和使用遮罩来完成,...我实际上实现了所有这些,您可以在我在Github上的一些较早的提交中找到它。但是事实证明,它比列表要慢。我猜,pypy确实优化了列表操作。