[PS] BOJ 10434 / 행복한 소수

[PS] BOJ 10434 / 행복한 소수
문제 링크: https://www.acmicpc.net/problem/10434
Thumbnail: Photo by The Travel Nook / Unsplash

'행복한 수' 를 판별하는 과정이 바로 떠오르지 않았지만, 패턴을 고찰해 보니 쉽게 이해할 수 있었습니다!

풀이

임의의 수 \(M\)가 행복한 소수인지 판단하려면, 다음 두 가지 과정을 거치면 된다.

  • \(M\)이 소수인가?
  • \(M\)이 행복한 수인가?

소수인지 판별하는 경우는 문제에서 \(1 \le M \le 10,000\) 라는 조건을 주었으니, 그냥 이 범위 안의 소수를 에라토스테네스의 체 알고리즘으로 미리 계산해 두면 된다.

행복한 수인지 판별하는 경우를 생각해 보자. 행복한 수란, 각 자리의 제곱의 합 계산하는 과정을 반복해 그 결과가 1이 되는 수를 말한다.

대표적인 예시로, 가장 작은 행복한 소수인 7이 행복한 수인지 판단하는 과정을 보자.

  • 7 → 72 = 49
  • 49 → 42 + 92 = 97
  • 97 → 92 + 72 = 130
  • 130 → 12 + 32 + 02 = 10
  • 10 → 12 + 02 = 1

각 자리의 제곱의 합을 계산하고, 그 결과가 1인지 비교하는 과정을 먼저 구현해보자.

while (condition):
    sum = 0
    while n > 0:
        sum += (n % 10) ** 2
        n //= 10
    if sum == 1:
        return True
    n = sum

하지만, 이대로는 행복한 수가 아닌 경우에는 while문을 벗어날 수 없다! 이를 조절할 적절한 조건을 찾아야 한다. 행복한 수가 아닌 어떤 수에 대해, 그 계산 과정을 분석해 보자.

  • 8 → 82 = 64
  • 64 → 62 + 42 = 52
  • 52 → 52 + 22 - 29
  • 29 → 22 + 92 = 85
  • 85 → 82 + 52 = 89
  • 89 → 82 + 92 = 145
  • 145 → 12 + 42 + 52 = 42
  • 42 → 42 + 22 = 18
  • 18 → 12 + 82 = 65
  • 65 → 62 + 52 = 61
  • 61 → 62 + 12 = 37
  • 37 → 32 + 72 = 58
  • 58 → 52 + 82 = 89
  • 89 → 82 + 92 = 145

계산 과정을 반복하다 보면, 어느새 같은 수가 나온 뒤로 같은 과정을 반복하고 있는걸 발견할 수 있다. 그에 따라, 계산 과정에서 거쳐가는 수들을 기록해 두었다가, 새 연산을 하기 전에 이미 기록한 수인지 확인하면 행복하지 않은 수를 찾아낼 수 있다.

def happy_number(n):
    """주어진 수가 '행복한 수'인지 확인한다.
    행복한 수는 각 자리 숫자의 제곱의 합을 구하는 연산을 반복해 1이 되는 수이다.

    Args:
        n (int): 행복한 수인지 확인할 자연수

    Returns:
        bool: 행복한 수인지 여부
    """
    trace = set()
    while n not in trace:
        trace.add(r)
        
        sum = 0
        while n > 0:
            sum += (n % 10) ** 2
            n //= 10
        if sum == 1:
            return True
        n = sum
    return False

코드

from sys import stdin

# 에라토스테네스의 체를 사용해 1부터 10,000까지의 소수를 미리 계산한다.
sieve = [True for _ in range(10_001)]
sieve[1] = False

for i in range(2, 100):
    if sieve[i]:
        j = 2
        while i * j <= 10_000:
            sieve[i * j] = False
            j += 1


def happy_number(n):
    """주어진 수가 '행복한 수'인지 확인한다.
    행복한 수는 각 자리 숫자의 제곱의 합을 구하는 연산을 반복해 1이 되는 수이다.

    Args:
        n (int): 행복한 수인지 확인할 자연수

    Returns:
        bool: 행복한 수인지 여부
    """
    trace = set()
    while n not in trace:
        trace.add(r)
        
        sum = 0
        while n > 0:
            sum += (n % 10) ** 2
            n //= 10
        if sum == 1:
            return True
        n = sum
    return False


P = int(stdin.readline().strip())
for _ in range(P):
    idx, M = map(int, stdin.readline().strip().split())
    # M이 소수인지 확인하고. 행복한 수인지 확인한 뒤 둘 다 참이라면 'YES', 그렇지 않다면 'NO'를 출력한다.
    print(f"{idx} {M} {'YES' if sieve[M] and happy_number(M) else 'NO'}")

solution.py