Problem 405: A Rectangular Tiling

View on Project Euler

Project Euler Problem 405 Solution

EulerSolve provides an optimized solution for Project Euler Problem 405, A Rectangular Tiling, with C++, Python, Java, and a step-by-step mathematical explanation.

Problem Summary Let \(f(n)\) denote the tiling count defined by the problem. The key fact used by the implementation is that this counting sequence already has an exact closed form, so the task is no longer to enumerate tilings but to evaluate that formula at an enormous index. The program is written in terms of \(f(10^k)\), and the actual Project Euler instance uses \(k=10^{18}\), so the target quantity is \(f(10^{10^{18}})\bmod 17^7\). Mathematical Approach Step 1: Start from the closed form The C++, Python, and Java implementations all begin with the identity $$f(n)=1-\frac{(-1)^n}{15}-\frac{4}{3}2^n+\frac{2}{5}4^n.$$ This means the original tiling combinatorics have been compressed into three exponential terms and a constant. Once this formula is known, the whole problem becomes modular arithmetic with very large exponents. Step 2: Why the formula still gives integers Although the expression contains fractions, the result is always an integer. Multiply both sides by \(15\): $$15f(n)=15-(-1)^n-20\cdot 2^n+6\cdot 4^n.$$ The right-hand side is divisible by \(3\) and by \(5\)....

Detailed mathematical approach

Problem Summary

Let \(f(n)\) denote the tiling count defined by the problem. The key fact used by the implementation is that this counting sequence already has an exact closed form, so the task is no longer to enumerate tilings but to evaluate that formula at an enormous index. The program is written in terms of \(f(10^k)\), and the actual Project Euler instance uses \(k=10^{18}\), so the target quantity is \(f(10^{10^{18}})\bmod 17^7\).

Mathematical Approach

Step 1: Start from the closed form

The C++, Python, and Java implementations all begin with the identity

$$f(n)=1-\frac{(-1)^n}{15}-\frac{4}{3}2^n+\frac{2}{5}4^n.$$

This means the original tiling combinatorics have been compressed into three exponential terms and a constant. Once this formula is known, the whole problem becomes modular arithmetic with very large exponents.

Step 2: Why the formula still gives integers

Although the expression contains fractions, the result is always an integer. Multiply both sides by \(15\):

$$15f(n)=15-(-1)^n-20\cdot 2^n+6\cdot 4^n.$$

The right-hand side is divisible by \(3\) and by \(5\). Modulo \(3\), we use \(2\equiv -1\pmod 3\), so

$$-(-1)^n-20\cdot 2^n\equiv -(-1)^n-2^{n+1}\equiv -(-1)^n-(-1)^{n+1}\equiv 0\pmod 3.$$

Modulo \(5\), we use \(4\equiv -1\pmod 5\), so

$$-(-1)^n+6\cdot 4^n\equiv -(-1)^n+4^n\equiv -(-1)^n+(-1)^n\equiv 0\pmod 5.$$

Hence the numerator is always divisible by \(15\), which explains why the sequence values are integers even though the closed form is written with rational coefficients.

Step 3: Move the formula into modular arithmetic

Let

$$M=17^7=410338673.$$

Because \(3\), \(5\), and \(15\) are all coprime to \(M\), they have modular inverses. Therefore the same formula can be evaluated modulo \(M\) as

$$f(n)\equiv 1-(-1)^n\cdot 15^{-1}-4\cdot 2^n\cdot 3^{-1}+2\cdot 4^n\cdot 5^{-1}\pmod M.$$

This is the exact form used by the implementation: the fractions are replaced by modular inverses, and every multiplication is reduced modulo \(M\).

Step 4: Reduce the gigantic exponent

The hard part is that \(n\) itself is huge. In the actual instance, \(n=10^{10^{18}}\). Since \(2\) and \(4\) are both coprime to \(17\), Euler's theorem applies. For a prime power \(17^7\),

$$\varphi(M)=17^7-17^6=16\cdot 17^6=386201104.$$

Hence

$$2^{\varphi(M)}\equiv 1\pmod M,\qquad 4^{\varphi(M)}\equiv 1\pmod M,$$

so exponents may be reduced modulo \(\varphi(M)\):

$$2^n\equiv 2^{\,n\bmod \varphi(M)}\pmod M,\qquad 4^n\equiv 4^{\,n\bmod \varphi(M)}\pmod M.$$

For the parameterized form \(n=10^k\), we therefore compute

$$e\equiv 10^k\pmod{\varphi(M)}$$

and then evaluate only \(2^e\) and \(4^e\) modulo \(M\). For the actual input \(k=10^{18}\), this reduction gives

$$10^{10^{18}}\equiv 152370032\pmod{386201104}.$$

So the astronomically large exponents collapse to ordinary modular powers with exponent \(152370032\).

Step 5: Use the parity of \(10^k\)

The sign term is even simpler. If \(k\ge 1\), then \(10^k\) is even, so

$$(-1)^{10^k}=1.$$

Only the degenerate case \(k=0\) gives \(n=1\), where the sign is negative. Therefore, for every real problem instance with \(k\ge 1\), the formula simplifies to

$$f(10^k)\equiv 1-15^{-1}-4\cdot 2^e\cdot 3^{-1}+2\cdot 4^e\cdot 5^{-1}\pmod M,$$

with \(e\equiv 10^k\pmod{\varphi(M)}\).

Worked Example

A small exact check is \(n=4\). Then

$$f(4)=1-\frac{1}{15}-\frac{4}{3}\cdot 16+\frac{2}{5}\cdot 256.$$

Putting everything over denominator \(15\),

$$f(4)=\frac{15-1-320+1536}{15}=\frac{1230}{15}=82.$$

This matches the checkpoint used in the implementation. Another useful consistency check is

$$f(10^9)\equiv 126897180\pmod{17^7}.$$

How the Code Works

The implementation performs only a constant number of fast modular exponentiations. First it computes \(\varphi(17^7)\). Next it finds \(10^k \bmod \varphi(17^7)\) by binary exponentiation, which is enough to recover the needed powers of \(2\) and \(4\). The rational coefficients are converted into modular inverses using Euler's theorem in the form \(a^{-1}\equiv a^{\varphi(M)-1}\pmod M\) for \(a\in\{3,5,15\}\). Finally the four terms of the closed form are assembled with modular addition and subtraction.

So the program never constructs \(10^{10^{18}}\) as an ordinary integer, never enumerates tilings, and never performs any dynamic programming over the combinatorial objects. Everything is reduced to a tiny number of modular operations.

Complexity Analysis

Binary exponentiation takes logarithmic time in the exponent. Therefore the full method costs

$$O(\log k+\log M)$$

time and \(O(1)\) memory. Since the modulus \(M=17^7\) is fixed in this problem, the practical cost is essentially \(O(\log k)\) with constant auxiliary space.

Footnotes and References

  1. Problem page: https://projecteuler.net/problem=405
  2. Euler's theorem: Wikipedia - Euler's theorem
  3. Modular multiplicative inverse: Wikipedia - Modular multiplicative inverse
  4. Modular exponentiation: Wikipedia - Modular exponentiation
  5. Euler totient function: Wikipedia - Euler's totient function

Problem 405 source code

C++

#include <cstdint>
#include <iostream>
#include <string>

namespace {

using i64 = long long;
using u64 = std::uint64_t;
constexpr u64 kMod = 410338673ULL;   // 17^7
constexpr u64 kPhi = 386201104ULL;   // phi(17^7)

struct Options {
    i64 k = 1000000000000000000LL;
    bool run_checkpoints = true;
};

bool parse_i64_after_prefix(const std::string& arg, const std::string& prefix, i64& value) {
    if (arg.rfind(prefix, 0U) != 0U) {
        return false;
    }
    const std::string tail = arg.substr(prefix.size());
    if (tail.empty()) {
        return false;
    }
    try {
        value = std::stoll(tail);
    } catch (...) {
        return false;
    }
    return true;
}

bool parse_arguments(int argc, char** argv, Options& options) {
    for (int i = 1; i < argc; ++i) {
        const std::string arg(argv[i]);
        if (arg == "--skip-checkpoints") {
            options.run_checkpoints = false;
            continue;
        }
        if (parse_i64_after_prefix(arg, "--k=", options.k)) {
            continue;
        }
        std::cerr << "Unknown argument: " << arg << '\n';
        return false;
    }
    return options.k >= 0;
}

u64 mod_pow(u64 base, u64 exp, u64 mod) {
    u64 result = 1 % mod;
    u64 cur = base % mod;
    u64 e = exp;
    while (e > 0) {
        if (e & 1ULL) {
            result = static_cast<u64>((__uint128_t)result * cur % mod);
        }
        cur = static_cast<u64>((__uint128_t)cur * cur % mod);
        e >>= 1ULL;
    }
    return result;
}

u64 f_mod_n(const u64 n, const u64 mod) {
    // Closed form:
    // f(n) = 1 - (-1)^n / 15 - (4/3) * 2^n + (2/5) * 4^n.
    const u64 inv3 = mod_pow(3, mod - mod / 17 - 1, mod);
    const u64 inv5 = mod_pow(5, mod - mod / 17 - 1, mod);
    const u64 inv15 = static_cast<u64>((__uint128_t)inv3 * inv5 % mod);

    const u64 sign = (n & 1ULL) ? (mod - 1ULL) : 1ULL;
    const u64 p2 = mod_pow(2, n, mod);
    const u64 p4 = mod_pow(4, n, mod);

    u64 ans = 1ULL;
    ans = (ans + mod - static_cast<u64>((__uint128_t)sign * inv15 % mod)) % mod;
    ans = (ans + mod - static_cast<u64>((__uint128_t)4ULL * p2 % mod * inv3 % mod)) % mod;
    ans = (ans + static_cast<u64>((__uint128_t)2ULL * p4 % mod * inv5 % mod)) % mod;
    return ans;
}

u64 solve(i64 k) {
    // n = 10^k, and we need f(n) mod 17^7.
    // For 2^n and 4^n modulo 17^7, reduce exponent modulo phi(17^7).
    const u64 n_mod_phi = mod_pow(10, static_cast<u64>(k), kPhi);

    // For k>=1, n is even => (-1)^n = 1. For k=0, n=1 is odd.
    if (k == 0) {
        return f_mod_n(1ULL, kMod);
    }

    // Use n_mod_phi for exponentiations; parity is known even.
    const u64 inv3 = mod_pow(3, kPhi - 1, kMod);
    const u64 inv5 = mod_pow(5, kPhi - 1, kMod);
    const u64 inv15 = static_cast<u64>((__uint128_t)inv3 * inv5 % kMod);
    const u64 p2 = mod_pow(2, n_mod_phi, kMod);
    const u64 p4 = mod_pow(4, n_mod_phi, kMod);

    u64 ans = 1ULL;
    ans = (ans + kMod - inv15) % kMod;  // (-1)^n = 1
    ans = (ans + kMod - static_cast<u64>((__uint128_t)4ULL * p2 % kMod * inv3 % kMod)) % kMod;
    ans = (ans + static_cast<u64>((__uint128_t)2ULL * p4 % kMod * inv5 % kMod)) % kMod;
    return ans;
}

bool run_checkpoints() {
    if (f_mod_n(1ULL, kMod) != 0ULL) {
        std::cerr << "Checkpoint failed: f(1)\n";
        return false;
    }
    if (f_mod_n(4ULL, kMod) != 82ULL) {
        std::cerr << "Checkpoint failed: f(4)\n";
        return false;
    }
    if (f_mod_n(1000000000ULL, kMod) != 126897180ULL) {
        std::cerr << "Checkpoint failed: f(10^9) mod 17^7\n";
        return false;
    }
    return true;
}

}  // namespace

int main(int argc, char** argv) {
    Options options;
    if (!parse_arguments(argc, argv, options)) {
        return 1;
    }
    if (options.run_checkpoints && !run_checkpoints()) {
        return 2;
    }

    std::cout << solve(options.k) << '\n';
    return 0;
}

Python

def mod_pow(base, exp, mod):
    return pow(base, exp, mod)

def solve():
    kMod = 410338673 # 17^7
    kPhi = 386201104 # phi(17^7)
    k = 1000000000000000000

    n_mod_phi = mod_pow(10, k, kPhi)

    inv3 = mod_pow(3, kPhi - 1, kMod)
    inv5 = mod_pow(5, kPhi - 1, kMod)
    inv15 = (inv3 * inv5) % kMod

    p2 = mod_pow(2, n_mod_phi, kMod)
    p4 = mod_pow(4, n_mod_phi, kMod)

    ans = 1
    ans = (ans - inv15) % kMod
    ans = (ans - 4 * p2 * inv3) % kMod
    ans = (ans + 2 * p4 * inv5) % kMod

    return str((ans + kMod) % kMod)

if __name__ == '__main__':
    print(solve())

Java

public class Euler405 {
    private static final long kMod = 410338673L; // 17^7
    private static final long kPhi = 386201104L; // phi(17^7)

    private static long modPow(long base, long exp, long mod) {
        long result = 1 % mod;
        long cur = base % mod;
        long e = exp;
        while (e > 0) {
            if ((e & 1L) != 0L) {
                result = (result * cur) % mod;
            }
            cur = (cur * cur) % mod;
            e >>= 1L;
        }
        return result;
    }

    public static String solve() {
        long k = 1000000000000000000L;

        long nModPhi = modPow(10, k, kPhi);

        long inv3 = modPow(3, kPhi - 1, kMod);
        long inv5 = modPow(5, kPhi - 1, kMod);
        long inv15 = (inv3 * inv5) % kMod;

        long p2 = modPow(2, nModPhi, kMod);
        long p4 = modPow(4, nModPhi, kMod);

        long ans = 1L;
        ans = (ans + kMod - inv15) % kMod;
        ans = (ans + kMod - (((4L * p2) % kMod) * inv3) % kMod) % kMod;
        ans = (ans + (((2L * p4) % kMod) * inv5) % kMod) % kMod;

        return String.valueOf(ans);
    }

    public static void main(String[] args) {
        System.out.println(solve());
    }
}