|
| 1 | +""" |
| 2 | +Problem 108: https://projecteuler.net/problem=108 |
| 3 | +
|
| 4 | +Problem Statement: |
| 5 | +
|
| 6 | +In the following equation x, y, and n are positive integers. |
| 7 | + 1/x + 1/y = 1/n |
| 8 | +
|
| 9 | +For n = 4 there are exactly three distinct solutions: |
| 10 | + 1/5 + 1/20 = 1/4 |
| 11 | + 1/6 + 1/12 = 1/4 |
| 12 | + 1/8 + 1/8 = 1/4 |
| 13 | +
|
| 14 | +What is the least value of n for which the number of distinct solutions |
| 15 | +exceeds one-thousand? |
| 16 | +
|
| 17 | +
|
| 18 | +Solution: |
| 19 | +
|
| 20 | +For a given n, the number of distinct solutions is (d(n * n) // 2) + 1, |
| 21 | +where d is the divisor counting function. Find an arbitrary n with more |
| 22 | +than 1000 solutions, so n is an upper bound for the answer. Find |
| 23 | +prime factorizations for all i < n, allowing easy computation of d(i * i) |
| 24 | +for i <= n. Then try all i to find the smallest. |
| 25 | +""" |
| 26 | + |
| 27 | + |
| 28 | +def find_primes(n : int) -> list[int]: |
| 29 | + """ |
| 30 | + Returns a list of all primes less than or equal to n |
| 31 | + >>> find_primes(19) |
| 32 | + [2, 3, 5, 7, 11, 13, 17, 19] |
| 33 | + """ |
| 34 | + sieve = [True] * (n + 1) |
| 35 | + |
| 36 | + for i in range(2, n + 1): |
| 37 | + for j in range(2 * i, n + 1, i): |
| 38 | + sieve[j] = False |
| 39 | + return [i for i in range(2, n + 1) if sieve[i]] |
| 40 | + |
| 41 | + |
| 42 | +def find_prime_factorizations(n : int) -> list[dict[int, int]]: |
| 43 | + """ |
| 44 | + Returns a list of prime factorizations of 2...n, with prime |
| 45 | + factorization represented as a dictionary of (prime, exponent) pairs |
| 46 | + >>> find_prime_factorizations(7) |
| 47 | + [{}, {}, {2: 1}, {3: 1}, {2: 2}, {5: 1}, {2: 1, 3: 1}, {7: 1}] |
| 48 | + """ |
| 49 | + primes = find_primes(n) |
| 50 | + prime_factorizations = [dict() for _ in range(n + 1)] |
| 51 | + |
| 52 | + for p in primes: |
| 53 | + for j in range(p, n + 1, p): |
| 54 | + j_factorization = prime_factorizations[j] |
| 55 | + x = j |
| 56 | + while x % p == 0: |
| 57 | + x /= p |
| 58 | + j_factorization[p] = j_factorization.get(p, 0) + 1 |
| 59 | + return prime_factorizations |
| 60 | + |
| 61 | + |
| 62 | +def num_divisors_of_square(prime_factorization : dict[int, int]) -> int: |
| 63 | + """ |
| 64 | + Returns the number of divisors of n * n, where n is the |
| 65 | + number represented by the input prime factorization |
| 66 | + >>> num_divisors_of_square({2: 2, 3: 2}) # n = 36 |
| 67 | + 25 |
| 68 | + """ |
| 69 | + num_divisors = 1 |
| 70 | + for _, e in prime_factorization.items(): |
| 71 | + num_divisors *= 2 * e + 1 |
| 72 | + return num_divisors |
| 73 | + |
| 74 | + |
| 75 | +def solution(target : int = 1000) -> int: |
| 76 | + """ |
| 77 | + Returns the smallest n with more than 'target' solutions |
| 78 | + >>> solution() |
| 79 | + 180180 |
| 80 | + """ |
| 81 | + |
| 82 | + upper_bound = 210 ** ((int((2 * target - 1) ** 0.25) + 1) // 2) |
| 83 | + prime_factorizations = find_prime_factorizations(upper_bound) |
| 84 | + |
| 85 | + def num_solutions(n): |
| 86 | + return (num_divisors_of_square(prime_factorizations[n]) // 2) + 1 |
| 87 | + |
| 88 | + for i in range(2, upper_bound + 1): |
| 89 | + if num_solutions(i) > target: |
| 90 | + return i |
| 91 | + |
| 92 | +if __name__ == "__main__": |
| 93 | + print(f"{solution() = }") |
0 commit comments