Project Euler Problem 663
Let tk be the tribonacci numbers defined as: quad t0 = t1 = 0; quad t2 = 1; quad tk = t{k-1} + t{k-2} + t{k-3} quad text
Solution
Answer: 1884138010064752
The key observation is that this is a dynamic maximum subarray problem under point updates.
Let
$$A_n = (A[0],A[1],\dots,A[n-1])$$
and after step $i$,
$$M_n(i)=\max_{0\le p\le q<n}\sum_{j=p}^q A[j]$$
(the maximum contiguous subarray sum, i.e. Kadane’s problem).
At each step only one position changes, so recomputing $M_n(i)$ from scratch in $O(n)$ is impossible for
$$n=10{,}000{,}003,\qquad l=10{,}200{,}000.$$
The efficient solution is:
- Generate tribonacci values modulo $n$ incrementally.
- Maintain $A_n$ with point updates.
- Maintain the global maximum subarray sum using a segment tree.
For each segment we store four quantities:
$$(\text{sum},\text{pref},\text{suff},\text{best})$$
where:
sum= total segment sumpref= maximum prefix sumsuff= maximum suffix sumbest= maximum subarray sum
Two child segments $L,R$ combine in $O(1)$:
$$\text{sum}=L_s+R_s$$
$$\text{pref}=\max(L_p,L_s+R_p)$$
$$\text{suff}=\max(R_u,R_s+L_u)$$
$$\text{best}= \max(L_b,R_b,L_u+R_p)$$
A memory-efficient implementation uses block decomposition: keep the full array $A$ in a compact 64-bit array and maintain a segment tree over blocks rather than individual elements. Since the problem only asks for
$$S(n,10{,}200{,}000)-S(n,10{,}000{,}000),$$
we only need $M_n(i)$ for the last $200{,}000$ steps. So:
- apply the first $10^7$ updates directly,
- build the structure once,
- process the final $200{,}000$ updates while accumulating $M_n(i)$.
A correct Python implementation is:
from array import array
NEG_INF = -(10**18)
def block_summary(A, start, end):
r = 0
max_pref = NEG_INF
min_pref_best = 0
best = NEG_INF
min_pref_suff = 0
last = end - 1
for k in range(start, end):
r += A[k]
max_pref = max(max_pref, r)
best = max(best, r - min_pref_best)
min_pref_best = min(min_pref_best, r)
if k != last:
min_pref_suff = min(min_pref_suff, r)
total = r
max_suff = total - min_pref_suff
return total, max_pref, max_suff, best
def build_block_tree(A, n, B):
m = (n + B - 1) // B
size = 1
while size < m:
size <<= 1
total = array("q", [0]) * (2 * size)
pref = array("q", [NEG_INF]) * (2 * size)
suff = array("q", [NEG_INF]) * (2 * size)
best = array("q", [NEG_INF]) * (2 * size)
# Leaves
for b in range(m):
s = b * B
e = min(n, s + B)
ts, tp, tu, tb = block_summary(A, s, e)
pos = size + b
total[pos] = ts
pref[pos] = tp
suff[pos] = tu
best[pos] = tb
# Internal nodes
for pos in range(size - 1, 0, -1):
l = pos * 2
r = l + 1
total[pos] = total[l] + total[r]
pref[pos] = max(pref[l], total[l] + pref[r])
suff[pos] = max(suff[r], total[r] + suff[l])
best[pos] = max(best[l], best[r],
suff[l] + pref[r])
return (m, size, total, pref, suff, best)
def update_block(tree, A, n, B, b):
m, size, total, pref, suff, best = tree
s = b * B
e = min(n, s + B)
ts, tp, tu, tb = block_summary(A, s, e)
pos = size + b
total[pos] = ts
pref[pos] = tp
suff[pos] = tu
best[pos] = tb
pos //= 2
while pos:
l = pos * 2
r = l + 1
total[pos] = total[l] + total[r]
pref[pos] = max(pref[l], total[l] + pref[r])
suff[pos] = max(suff[r], total[r] + suff[l])
best[pos] = max(best[l], best[r],
suff[l] + pref[r])
pos //= 2
def solve():
n = 10_000_003
start = 10_000_000
end = 10_200_000
B = 256
A = array("q", [0]) * n
# tribonacci window modulo n
u0, u1, u2 = 0, 0, 1 % n
tree = None
ans = 0
for i in range(1, end + 1):
idx = u0
delta = (u1 << 1) - n + 1
A[idx] += delta
if i == start:
tree = build_block_tree(A, n, B)
elif i > start:
update_block(tree, A, n, B, idx // B)
ans += tree[5][1] # root maximum subarray sum
# advance tribonacci by 2 steps
t3 = u0 + u1 + u2
if t3 >= n:
t3 -= n
if t3 >= n:
t3 -= n
t4 = u1 + u2 + t3
if t4 >= n:
t4 -= n
if t4 >= n:
t4 -= n
u0, u1, u2 = u2, t3, t4
return ans
print(solve())
Verification on the given test cases:
- $S(5,6)=32$
- $S(5,100)=2416$
- $S(14,100)=3881$
- $S(107,1000)=1618572$
all match exactly.
Running the full computation gives:
Answer: 1884138010064752