diff --git a/python/e800.py b/python/e800.py new file mode 100644 index 0000000..1dcfa2f --- /dev/null +++ b/python/e800.py @@ -0,0 +1,68 @@ +from lib_prime import primes +from math import log, ceil + + +def c(n_base: int) -> int: + """Compute the number of prime pairs (p, q) where + p**q * q**p <= n_base**n_base and p < q""" + digits = ceil(log(n_base, 10)) + 1 + n = n_base**n_base + n_log = log(n) + ps = primes(2 * 10**digits) + p, q = ps[0], ps[-1] + assert (p**q * q**p) > n, "largest prime too low" + j_max = len(ps) - 1 + + t = 0 + for i in range(len(ps)): + p, q = ps[i], ps[i + 1] + + # Crux: + # (p**q * q**p) > n + # <=> ln(p**q * q**p) > ln(n) + # <=> ln(p**q) + ln(q**p) > ln(n) + # <=> q * ln(p) + p * ln(q) > ln(n) + # Much lower numbers and therefore faster to compute. + if q * log(p) + p * log(q) > n_log: + break + + # Binary search to find the largest j for which p**q * q**p <= n: + j_lo, j_hi = i + 1, j_max + while (j_hi - j_lo) > 1: + j_mid = (j_hi + j_lo) // 2 + p, q = ps[i], ps[j_mid] + if q * log(p) + p * log(q) > n_log: + j_hi = j_mid + else: + j_lo = j_mid + j_max = j_lo + 1 + + # All primes lower than ps[j_lo] have to be counted. + t += j_lo - i + + # p_ref, q_ref = ps[i], ps[j_lo] + # print(p_ref, q_ref, "<=") + + # Turns out the binary search is not necessary with the optimized + # version of the comparison and the following works just as fine. + # for j in range(j_max, i, -1): + # p, q = ps[i], ps[j] + # if q * log(p) + p * log(q) > n_log: + # j_max = j + # else: + # assert p == p_ref + # assert q == q_ref + # t += (j - i) + # break + return t + + +def euler_800(): + assert c(800) == 10790 + return c(800800) + + +if __name__ == "__main__": + solution = euler_800() + print(f"e800.py: {solution}") + assert(solution == 1412403576)