我对@987654321@ 的答案的两个方面很感兴趣:计算速度(即使对于巨大的数字),以及结果倾向于具有小的素因数。这些暗示可以将解决方案计算为小项的乘积,事实证明确实如此。
为了解释发生了什么,我需要一些符号和术语。对于任何非负整数n,我将n 的(唯一)贪婪表示定义为通过重复取不超过n 的最大斐波那契数获得的斐波那契数之和。例如,10 的贪心表示是8 + 2。很容易观察到,我们从未在如此贪婪的表示中使用Fib(1)。
我还想要一种紧凑的方式来编写这些表示,为此我将使用位串。很像二进制,除了位值遵循斐波那契数列而不是 2 的幂数列,我会先写最低有效位。例如00100001 在位置2 和位置7 中有1s,所以代表Fib(2) + Fib(7) = 1 + 13 = 14。 (是的,我从0 开始计数,并遵循Fib(0) = 0 的惯例。)
找到所有表示的蛮力方法是从贪婪表示开始,然后探索将001 形式的子模式重写为110 形式的模式的所有可能性;即,对于某些 k,将 Fib(k+2) 替换为 Fib(k) + Fib(k+1)。
所以我们总是可以将n 的贪心表示写为位串,并且该位串将是0s 和1s 的序列,没有两个相邻的1s。现在关键的观察是我们可以将这个比特串分成几部分,并计算每个单独部分的重写次数,乘以得到表示的总数。这是有效的,因为位串中的某些子模式阻止了模式左侧字符串部分的重写规则与右侧的重写规则之间的交互。
例如,让我们看一下n = 78。它的贪婪表示是00010000101,并且一种蛮力方法可以快速识别全套表示。其中有十个:
00010000101
01100000101
00010011001
01100011001
00011101001
01101101001
0001001111
0110001111
0001110111
0110110111
我们可以将模式的第一部分 0001 与第二部分 0000101 分开。上面的每个组合都来自重写0001,分别重写0000101,并将两个重写粘合在一起。模式的左侧部分有 2 次重写(包括原版),右侧有 5 次重写,因此我们总共得到 10 种表示形式。
使这项工作有效的原因是左半部分0001 的任何重写都以01 或10 结尾,而右半部分的任何重写都以00 或11 开头。因此,与边界重叠的 001 或 110 都不匹配。只要我们有两个由 偶数 个零分隔的 1s,我们就会得到这种分隔。
这解释了 Niklas 的回答中看到的小素因数:在随机选择的数字中,会有许多偶数长度的 0s 序列,每个序列都代表一个我们可以拆分计算的点。
解释变得有点复杂,所以这里有一些 Python 代码。我已经验证结果与 Niklas 的所有n 直到10**6 以及随机选择的大n 的选择一致。它应该具有相同的算法复杂度。
def combinations(n):
# Find Fibonacci numbers not exceeding n, along with their indices.
# We don't need Fib(0) or Fib(1), so start at Fib(2).
fibs = []
a, b, index = 1, 2, 2
while a <= n:
fibs.append((index, a))
a, b, index = b, a + b, index + 1
# Compute greedy representation of n as a sum of Fibonacci numbers;
# accumulate the indices of those numbers in indices.
indices = []
for index, fib in reversed(fibs):
if n >= fib:
n -= fib
indices.append(index)
indices = indices[::-1]
# Compute the 'signature' of the number: the lengths of the pieces
# of the form 00...01.
signature = [i2 - i1 for i1, i2 in zip([-1] + indices[:-1], indices)]
# Iterate to simultaneously compute total number of rewrites,
# and the total number with the top bit set.
total, top_set = 1, 1
for l in signature:
total, top_set = ((l + 2) // 2 * total - (l + 1) % 2 * top_set, total)
# And return the grand total.
return total
编辑:大大简化了代码。
编辑2:我刚刚再次遇到这个答案,并怀疑有更直接的方法。这是另一个代码简化,清楚地表明需要O(log n) 操作。
def combinations(n):
"""Number of ways to write n as a sum of positive
Fibonacci numbers with distinct indices.
"""
# Find Fibonacci numbers not exceeding n.
fibs = []
fib, next_fib = 0, 1
while fib <= n:
fibs.append(fib)
fib, next_fib = next_fib, fib + next_fib
# Compute greedy representation, most significant bit first.
greedy = []
for fib in reversed(fibs):
greedy.append(fib <= n)
if greedy[-1]:
n -= fib
# Iterate to compute number of rewrites.
x, y, z = 1, 0, 0
for bit in reversed(greedy):
x, y, z = (0, y + z, x) if bit else (x + z, z, y)
return y + z