from lib_misc import gcd from lib_misc import is_permutation class Primes(object): def __init__(self, n_max): import bitarray self.n_max = n_max b = bitarray.bitarray(n_max) b.setall(True) n = 1 b[n - 1] = False while n * n <= n_max: if b[n - 1] is True: for i in range(n + n, n_max + 1, n): b[i - 1] = False n += 1 self.b = b def iter_down(self): for i in range(self.n_max, 0, -1): if self.b[i - 1]: yield i raise StopIteration def iter_up(self): for i in range(1, self.n_max + 1): if self.b[i - 1]: yield i raise StopIteration def iter_range(self, n_min, n_max): for i in range(n_min, n_max + 1): if self.b[i - 1]: yield i raise StopIteration def is_prime(self, n): if n > self.n_max: raise Exception("n greater than n_max") return self.b[n - 1] def relative_primes_count_naiv(n): return len([i for i in range(1, n) if gcd(n, i) == 1]) def relative_primes_count_factors(n, fs): """ XXX: this method has a bug that first occurs for n = 60. Use totient function from e072.py instead! For some curious reason it is good enough for this problem. Probably because we only deal with two factors ever. """ from itertools import combinations rel_primes_count = n - 1 # n itself is not a relative prime for f in fs: rel_primes_count -= (n // f - 1) for f_1, f_2 in combinations(fs, 2): f = f_1 * f_2 rel_primes_count += (n // f - 1) return rel_primes_count def get_phi(n): r = relative_primes_count_naiv(n) return n / r def get_phi_factors(n, fs): r = relative_primes_count_factors(n, fs) return n / r def euler_070(): """ I struggled harder than I should have with this problem. I realized quickly that a prime can't be the solution because a prime minus one cannot be a permutation of itself. I then figured that the solution is probably a number with two prime factors. I implemented an algorithm, but it did not yield the right solution. I tried a couple of things like squaring one prime factor which does not yield a solution at all and three factors. Finally, I came up with a faster algorithm to get the number of relative primes faster. With that procedure I was then able to bruteforce the problem in 10 minutes. When analyzing the solution I saw that it actually consists of two primes which means my orginal algorithm had a bug. After reimplenting it was able to find the solution in under 30 seconds. We could further optimize this by making the search range for the two factors smaller. """ n = 10**7 ps = Primes(n // 1000) phi_min = 1000 n_phi_min = 0 for p_1 in ps.iter_down(): for p_2 in ps.iter_range(1000, n // 1000): n_new = p_1 * p_2 if n_new > n: break rel_primes_n_new = relative_primes_count_factors(n_new, [p_1, p_2]) phi_new = n_new / rel_primes_n_new if phi_new < phi_min and is_permutation(n_new, rel_primes_n_new): phi_min = phi_new n_phi_min = n_new return n_phi_min if __name__ == "__main__": print("e070.py: " + str(euler_070())) assert(euler_070() == 8319823)