프로젝트 오일러 046
(소수 + 2×제곱수)로 나타낼 수 없는 가장 작은 홀수 합성수 찾기
문제
골드바흐의 다른 추측
크리스티안 골드바흐는 원래 '골드바흐의 추측'이라는 문제로 유명합니다. 2보다 큰 모든 짝수는 2개 소수의 합으로 나타낼 수 있다는 것입니다. 이 문제에서 소개된 가설은 '골드바흐의 다른 추측'으로 불립니다.
어떤 홀수합성수를 m이라 하고, 이를 어떤 소수 p와 다른 자연수 n의 조합으로 나타낸다면, 수식으로 쓰면 다음과 같습니다.
m = p + 2 × (n · n)
p와 n이 모두 정해지지 않았기 때문에 1) m보다 작은 모든 소수 p에 대해서 이 식을 만족하는 자연수 n이 있는지 살펴보거나, 2) 가장 작은 소수 2를 대입하여 sqrt((m - 2) ÷ 2) 보다 작은 모든 n에 대해서 수식을 만족하는 p가 소수인지를 검사하는 방법이 있습니다.
이 두 가지 방법 중에서 저는 2) 의 방법을 선택할 것입니다. 100의 제곱근은 10 밖에 되지 않지만 100 미만의 소수는 25개나 있습니다. 조금 더 범위가 늘어나면 이 차이는 극적으로 벌어집니다. 1000의 제곱근은 31보다 조금 더 큰 수인데, 1000 보다 작은 소수는 168개나 있습니다. 따라서 m과 p, n의 관계식에서 n = 1, 2, ···, sqrt((m - 2) ÷ 2) 일 때, 각각의 p가 소수인지를 검사하여 조건을 만족하는 n, p가 하나라도 존재한다면 소수와 합성수의 제곱으로 표현할 수 있는 수로 보면 됩니다.
from functools import wraps
# M = p + 2×(n^2)
# n = sqrt((M - p) ÷ 2)
_cache: dict[int, bool] = {}
def isprime(n: int) -> bool:
if n in _cache:
return _cache[n]
res = True
if n < 2:
return False
if n < 4:
return True
if n % 2 == 0 or n % 3 == 0:
return False
if n < 10:
return True
k, l = 5, int(n ** 0.5 + 1.5)
while k < l:
if n % k == 0 or n % (k + 2) == 0:
res = False
break
k += 6
_cache[n] = res
return res
def test(m: int) -> bool:
if isprime(m):
return False
l = int(((m - 2) / 2) ** 0.5 + 1.5)
for i in range(1, l):
p = m - (i * i) * 2
if isprime(p):
# print(f'{m} = {p} * 2×{i}^2')
return False
return True
def main():
m = 9
while True:
if test(m):
return m
m += 2
if __name__ == '__main__':
print(main())
작은 값이지만 같은 값에 대한 소수 체크를 계속 반복하기 때문에 캐싱을 통해서 반복을 피하면 시간을 크게 단축할 수 있습니다. 그리고 이 추측은 '홀수 합성수'를 대상으로 하기 때문에, m이 소수이지 않도록 체크하는 부분도 빼놓지 않아야 합니다.
참고로 이 문제의 답 역시 그리 크지 않은 수입니다. 그 보다 더 큰 값도 존재합니다만, 몇 개의 반례를 찾은 후에는 천만 이하의 홀수 합성수에 대해서는 오히려 이 추측에 대한 반례를 찾기 어렵습니다, m이 일정 크기 이상으로 커지면 이 추축에 끼워맞출 수 있는 p, n을 찾을 만큼 p가 충분히 많기 때문일 것으로 생각됩니다.