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

Project Euler Problem 663

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:

  1. Generate tribonacci values modulo $n$ incrementally.
  2. Maintain $A_n$ with point updates.
  3. 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 sum
  • pref = maximum prefix sum
  • suff = maximum suffix sum
  • best = 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