문제풀이/DP

[Python/파이썬] 백준 16400번 소수 화폐

딜레이레이 2025. 4. 11. 15:54

https://www.acmicpc.net/problem/16400

코드

n = int(input())


def prime_number(n):
    is_prime = [True]*(n+1)
    is_prime[0] = False
    is_prime[1] = False

    prime = []

    for i in range(2, n+1):
        if is_prime[i]:
            for j in range(i*2, n+1, i):
                is_prime[j] = False
            prime.append(i)
    return prime


prime = prime_number(n)
dp = [0]*(n+1)
dp[0] = 1
for p in prime:
    for j in range(p, n+1):
        dp[j] = (dp[j] + dp[j-p]) % 123_456_789

print(dp[n])

 

에라토스테네스의 체를 이용하여 N 이하의 소수들을 구한 뒤, 이 소수들을 갖고 각 금액을 만들 수 있는 가짓수를 구하는 코드이다.

금액을 만들 수 있는 가짓수를 구하는 데에는 DP를 사용했다.

1. 크기가 N+1이고, 원소들은 모두 0인 dp 배열을 만든다. 이때 인덱스는 금액, 원소 값은 소수 동전을 사용하여 해당 금액을 만들 수 있는 가짓수이다.

2. 동전을 아무것도 사용하지 않았을 때 0원을 만들 수 있으므로 dp[0] = 1이다.

3. 소수 동전을 하나씩 추가한다고 생각하고 dp 배열의 값을 채워나간다. 예를 들어, 2원 동전을 추가했을 때, j라는 금액을 만들 수 있는 경우는 j-2원에 2원을 하나 더한 것과 같다. 그렇기 때문에 dp[j] = dp[j] + dp[j-p]가 되는 것이다.

 

그런데 이 코드는 PyPy3로는 통과하지만, Python3로는 시간 초과가 발생했다. 에라토스테네스의 체는 시간 복잡도가 O(n * log(log n))으로 효율적인 알고리즘인데도 시간 초과가 발생했다. Python3는 인터프리터 언어이기 때문에 처리 속도에 한계가 있기 때문인것 같다.

 

시간을 더 줄이기 위해 에라토스테네스의 체를 좀 더 최적화했다.

from math import sqrt

def prime_number(n):
    is_prime = [True]*(n+1)
    is_prime[0] = False
    is_prime[1] = False

    for i in range(2, int(sqrt(n))+1):
        if is_prime[i]:
            for j in range(i*i, n+1, i):
                is_prime[j] = False

    prime = []
    for i in range(2, n+1):
        if is_prime[i]:
            prime.append(i)
    return prime

 

두 가지를 개선했다.

 

1. 제곱근까지만 반복하도록 변경: N의 제곱근보다 큰 소수 p에 대해서는, p^2가 이미 N을 초과하기 때문에 확인할 필요가 없다. 즉, N 이하의 모든 합성수는 N 제곱근 이하의 소수들의 배수로 표현될 수 있으므로, N의 제곱근까지만 검사하면 충분하다.

 

2. 내부 반복문의 시작점을 i*2에서 i*i로 변경: 소수 i의 배수 중 i*i보다 작은 수들(i*2, i*3, ..., i*(i-1))은 이미 더 작은 소수들의 배수로서 처리되었기 때문이다. 예를 들어, 소수 5의 경우 5*2=10, 5*3=15, 5*4=20은 이미 소수 2와 3의 배수로서 처리되었으므로, 5*5=25부터 시작하면 된다.

 

근데 이것도 Python3로는 여전히 시간 초과가 발생한다.

해결 방법을 아신다면....댓글 주세요