使用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向量的搜索空间
我注意到与Akuroi 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 = 153分钟之内
码:
有关完整的代码,请访问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确实优化了列表操作。