I was intrigued by two aspects of Niklas B.'s answer: the speed of the computation (even for huge numbers), and the tendency of the results to have small prime factors. These hint that the solution can be computed as a product of small terms, and that indeed turns out to be the case.
To explain what's going on, I need some notation and terminology. For any nonnegative integer n
, I'll define the (unique) greedy representation of n
to be the sum of Fibonacci numbers obtained by repeatedly taking the largest Fibonacci number not exceeding n
. So for example, the greedy representation of 10
is 8 + 2
. It's easy to observe that we never use Fib(1)
in such a greedy representation.
I also want a compact way to write these representations, and for that I'm going to use bitstrings. Much like binary, except that the place values follow the Fibonacci sequence instead of the sequence of powers of 2, and I'll write with the least significant digit first. So for example 00100001
has 1
s in position 2
and position 7
, so represents Fib(2) + Fib(7) = 1 + 13 = 14
. (Yes, I'm starting counting at 0
, and following the usual convention that Fib(0) = 0
.)
A brute force way of finding all representations is to start with the greedy representation and then explore all possibilities for rewriting a subpattern of the form 001
as a pattern of the form 110
; i.e., replacing Fib(k+2)
with Fib(k) + Fib(k+1)
for some k
.
So we can always write the greedy representation of n
as a bitstring, and that bitstring will be a sequence of 0
s and 1
s, with no two adjacent 1
s. Now the key observation is that we can partition this bitstring into pieces and compute the number of rewrites for each separate piece, multiplying to get the total number of representations. This works because certain subpatterns in the bitstring prevent interaction between the rewrite rules for the portion of the string to the left of the pattern, and those to the right.
For an example, let's look at n = 78
. Its greedy representation is 00010000101
, and a brute-force approach quickly identifies the full set of representations. There are ten of them:
00010000101
01100000101
00010011001
01100011001
00011101001
01101101001
0001001111
0110001111
0001110111
0110110111
We can separate the first part of the pattern, 0001
, from the second, 0000101
. Each of the combinations above comes from rewriting 0001
, separately rewriting 0000101
, and glueing the two rewrites together. There are 2 rewrites (including the original) for the left portion of the pattern, and 5 for the right, so we get 10 representations overall.
What makes this work is that any rewrite of the left half, 0001
, ends with either 01
or 10
, while any rewrite of the right half starts with 00
or 11
. So there's no match for either 001
or 110
that overlaps the boundary. We'll get this separation whenever we have two 1
s separated by an even number of zeros.
And this explains the small prime factors seen in Niklas's answer: in a randomly-chosen number, there will be many sequences of 0
s of even length, and each of those represents a point where we can split the computation.
The explanations are becoming a little convoluted, so here's some Python code. I've verified that the results agree with Niklas's for all n
up to 10**6
, and for a selection of randomly chosen large n
. It should have the same algorithmic complexity.
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
EDIT: Code substantially simplified.
EDIT 2: I just came across this answer again, and suspected that there was a more straightforward way. Here's yet another code simplification, showing clearly that O(log n)
operations are required.
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