[백준] 9020. 골드바흐의 추측

방법이있지·2025년 5월 17일

백준 / 실버 2 / 9020. 골드바흐의 추측

  • 많은 양의 숫자를 하나하나씩 소수인지 아닌지 매번 확인해야 하는 문제
  • 아래와 같은 알고리즘으로, 숫자의 소수 여부를 확인할 수 있음
def check_prime(x):
    if x == 1:
        return False
    for i in range(2, int(math.sqrt(x)) + 1):
        if x % i == 0:
            return False
    return True
  • 하지만 O(N)O(\sqrt{N})의 시간 복잡도... 조금 더 빠르게 소수 여부를 확인할 순 없을까?

에라토스테네스의 체

  • 주어진 범위 내의 모든 소수를 효율적으로 구하는 알고리즘
  • 처음엔 모든 수가 소수라고 가정하고, 소수가 아닌 수를 하나씩 없애는 방식

절차

  • (1) 22부터 nn까지 자연수 나열
  • (2) 남은 숫자 중 가장 작은 자연수 pp를 선택한 뒤, pp 자신을 제외한 pp의 배수를 모두 지움
  • (3) pnp \leq \sqrt{n}까지 (2)를 반복
  • (4) 남은 수는 모두 소수
# 에라토스테네스의 체
# 2부터 N까지 숫자 나열
is_prime = [True] * (n + 1)     # 소수인 경우 True, 아니면 False를 담은 배열
is_prime[1] = False             # 1은 소수가 아님

# sqrt(N)까지 아래 과정을 반복
for i in range(2, int(math.sqrt(n)) + 1):
    # 남은 숫자 중 가장 작은 숫자 -> i
    if is_prime[i]:
        # i의 배수를 모두 지움 (i * i 이전의 배수는 이미 지워졌음에 유의)
        for j in range(i * i, n + 1, i):
            is_prime[j] = False
  • (2) 왜 pp의 배수 중 p×pp \times p부터 확인하나요?
    • p×(p1)p \times (p-1)까지의 배수는 앞에서 이미 지웠기 때문
    • e.g., p=3p=3일 때 6=3×26 = 3 \times 2는 2의 배수 지울 때 이미 지워짐
  • (3) 왜 pnp \leq \sqrt{n}까지만 구해도 되나요?
    • p>np > \sqrt{n}일때 pp의 모든 배수는 앞에서 이미 지웠기 때문
    • e.g., n=20n=20일 때 n=4.xx\sqrt{n} = 4.\text{xx}
      • 이때 44보다 더 큰 55의 배수 1010, 2020(22의 배수), 1515(33의 배수)는 이미 앞에서 지움

시간 복잡도

  • 22부터 NN까지 중 소수를 구할 떄
    • 22의 배수 -> 약 N2\frac{N}{2}개의 수를 지움
    • 33의 배수 -> 약 N3\frac{N}{3}개의 수를 지움
    • 55의 배수 -> 약 N5\frac{N}{5}개의 수를 지움
    • 이를 다 더하면 N(i는소수1i)N(\displaystyle\sum_{i는 소수}\frac{1}{i})개의 수를 지움
    • (i는소수1i)(\displaystyle\sum_{i는 소수}\frac{1}{i})는 수학적으로 loglogN\log \log N으로 근사됨
  • 체를 만들 때 O(NloglogN)O(N \log \log N)
  • 배열 접근은 O(1)O(1)이므로, 체를 한 번 만들어 두면 빠르게 소수 여부 판별 가능

풀이 및 시간 복잡도

  • 에라토스테네스의 체를 사용하지 않은 풀이
import math

def check_prime(x):
    if x == 1:
        return False
    for i in range(2, int(math.sqrt(x)) + 1):
        if x % i == 0:
            return False
    return True

T = int(input())

for _ in range(T):
    n = int(input())
    for i in range(n // 2, 0, -1):
        if check_prime(i) and check_prime(n - i):
            print(i, n - i)
            break
  • 시간 복잡도
    • 최대 N{N}번, O(N)O(\sqrt{N})의 시간 복잡도를 갖는 check_prime 함수 실행
    • 위 연산을 TT회 반복
    • 최종 O(T×NN)O(T \times N\sqrt{N})
  • 에라토스테네스의 체를 사용한 풀이
import math

T = int(input())
nums = []

for _ in range(T):
    nums.append(int(input()))
max_n = max(nums)

# 에라토스테네스의 체
# 2부터 N까지 숫자 나열
is_prime = [True] * (max_n + 1)     # 소수인 경우 True, 아니면 False
is_prime[1] = False                 # 1은 소수가 아님

# sqrt(N)까지 아래 과정을 반복
for i in range(2, int(math.sqrt(max_n)) + 1):
    # 남은 숫자 중 가장 작은 숫자 -> i
    if is_prime[i]:
        # i의 배수를 모두 지움 (i * i 이전의 배수는 이미 지워졌음에 유의)
        for j in range(i * i, max_n + 1, i):
            is_prime[j] = False

for n in nums:
    for i in range(n // 2, 0, -1):
        if is_prime[i] and is_prime[n - i]:
            print(i, n - i)
            break
  • 시간 복잡도
  • 이때 에라토스테네스의 체는 딱 1번만 만들고, 모든 테스트 케이스에서 사용된다는 점에 유의할 것
    • TT번의 테스트 케이스를 입력받으며, 체를 만들 때 필요한 최댓값을 구함: O(T)O(T)
    • 에라토스테네스의 체를 만듦: O(NloglogN)O(N \log \log N)
    • TT번의 테스트 케이스에 대해 최대 N{N}번, is_prime 배열에서 소수 여부를 확인: O(T×N)O(T \times N)
    • 최종 O(T×N+NloglogN)O(T \times N + N \log \log N)

실제 소요 시간 비교

  • 맨 밑에 412ms 걸린 풀이가 에라토스테네스의 체를 사용하지 않은 풀이
  • 위에 168ms 걸린 풀이가 에라토스테네스의 체를 사용한 풀이
  • 시간제한이 2초라 두 풀이 다 정답으로 인정됐지만, 암튼 약 3배 빠르다는 점을 알 수 있음
profile
뭔가 만드는 걸 좋아하는 개발자 지망생입니다. 프로야구단 LG 트윈스를 응원하고 있습니다.

0개의 댓글