[Dynamic Programming] 효율적인 화폐 구성

Update:     Updated:

카테고리:

태그:

효율적인 화폐 구성

난이도 : ⭐⭐

N가지 종류의 화폐가 있다. 이 화폐들의 개수를 최소한으로 이용해서 그 가치의 합이 M원이 되도록 하려고 한다. 이때 각 화폐는 몇 개라도 사용할 수 있으며, 사용한 화폐의 구성은 같지만 순서만 다른 것은 같은 경우로 구분한다. 예를 들어 2원, 3원 단위의 화폐가 있을 때는 15원을 만들기 위해 3원을 5개 사용하는 것이 가장 최소한의 화폐 개수이다.

입력 조건

  • 첫째 줄에 N, M이 주어진다. (1 <= N <= 100, 1 <= M <= 10,000)
  • 이후 N개의 줄에는 각 화폐의 가치가 주어진다. 화폐 가치는 10,000보다 작거나 같은 자연수이다.

출력 조건

  • 첫째 줄에 M원을 만들기 위한 최소한의 화폐 개수를 출력한다.
  • 불가능할 때는 -1을 출력한다.

입력 예시1
2 15
2
3

출력 예시1
5

입력 예시2
3 4
3
5
7

출력 예시2
-1

나의 풀이

다시 풀어보기

문제 해설

이 문제는 그리디에서 다루었던 거스름돈 문제와 거의 동일하다. 단지 화폐 단위에서 큰 단위가 작은 단위의 배수가 아니라는 점만 다르다. 그렇기 때문에 그리디 알고리즘을 사용했던 예시처럼 매번 가장 큰 화폐 단위부터 처리하는 방법으로는 해결할 수 없고 다이나믹 프로그래밍을 이용해아 한다.

이번 문제는 작은 금액부터 큰 금액까지 확인하며 차례대로 만들 수 있는 최소한의 화폐 개수를 찾으면 된다. 금액 i를 만들 수 있는 최소한의 화폐 개수를 $a_i$, 화폐의 단위를 k라고 했을 때 다음과 같이 점화식을 작성할 수 있다. $a_i-k$는 금액 (i - k)를 만들 수 있는 최소한의 화폐 개수를 의미한다.

  • $a_i-k$를 만드는 방법이 존재하는 경우, $a_i$ = min($a_i$, $a_i-k$ + 1)
  • $a_i-k$를 만드는 방법이 존재하지 않는 경우, $a_i$ = 10,001

이 점화식을 모든 화폐 단위에 대하여 차례대로 적용하면 된다. 실제로 문제를 풀기 위해서는 가장 먼저 K의 크기만큼 리스트를 할당한다. 이후에 각 인덱스를 ‘금액’으로 고려하여 메모이제이션을 진행한다. 예를 들어 N = 3, K = 7이고, 각 화폐의 단위가 2, 3, 5인 경우를 생각해보자.

  • 초기화 : 각 인덱스에 해당하는 값으로 10,001을 설정한다. 10,001은 특정 금액을 만들 수 있는 화폐 구성이 가능하지 않다는 의미이다. M의 최대 크기가 10,000이므로 불가능한 수로 10,001이라는 값을 설정했으며 이보다 더 큰 수여도 상관없다. 또한 0원의 경우, 화폐를 하나도 사용하지 않았을 때 만들 수 있으므로 값으로 0을 설정한다. 따라서 초기 리스트의 값은 다음과 같다.

image

  • 화폐 단위 : 2, 3, 5 가장 먼저 첫 번째 화폐 단위인 2부터 확인한다. 앞서 언급한 점화식에 따라 다음과 같이 리스트가 갱신된다. 예를 들어 인덱스 2의 경우 1이라는 값을 가지는데, 이는 2원짜리 화폐 하나를 이용하여 2원을 만들 수 있다는 의미이다. 다시 말해 $a_2$ = $a_0$ + 1이다. 인덱스 4의 경우 2라는 값을 가지는데, 이는 2원짜리 화폐를 2개 이용하여 (2 + 2) = 4원을 만들 수 있다는 의미이다. 다시 말해 $a_4$ = $a_2$ + 1이다. 몇 인덱스의 경우 10,001의 값을 그대로 가지는데, 이는 2원짜리 화폐를 가지고 구성할 수 없는 금액이기 때문이다. 예를 들어 인덱스 3의 경우 인덱스 1의 값이 10,001이므로 마찬가지로 10,001의 값을 가진다.

image

  • 화폐 단위 : 2, 3, 5 이어서 화폐 단위 3을 확인한다. 앞서 언급한 점화식에 따라서 값을 도출하면 다음과 같이 리스트가 갱신된다. 예를 들어 $a_5$ = $a_2$ + 1로 2라는 값을 가진다. 이것은 2원짜리 화폐 1개, 3원짜리 1개로 (2 + 3) = 5원을 만들 수 있다는 의미가 된다.

image

  • 화폐 단위 : 2, 3, 5 이어서 화폐 단위 5를 확인한다. 앞서 언급한 점화식에 따라서 값을 도출하면 다음과 같이 리스트가 갱신된다. 예를 들어 $a_7$ = $a_2$ + 1로 2라는 값을 가진다. 이는 2원짜리 화폐 1개, 5원짜리 화폐 1개로 (2 + 5) = 7원을 만들 수 있다는 의미가 된다. 원래 이전 단계에서 $a_7$의 값은 3이었는데, 이는 (2 + 2 + 3) = 7원으로 3개의 화폐를 사용했을 때를 나타낸 것이다. 다만, 현재 단계에서 (2 + 5) = 7원을 만들면 화폐를 2개를 사용해도 되므로, 더 작은 값으로 갱신된다.

image

아래의 코드에서 d[j - array[i]]가 10001인지 검사하는 부분은 사실 없어도 되는 코드인데. d[j - array[i]]가 10001의 값을 가지더라도 min(d[j], d[j - array[i]] + 1)은 항상 d[j]의 값을 반환하기 때문이다. 다만, 이해를 돕기 위해 코드에는 그대로 넣어주었다.

답안 예시

# 정수 N, M을 입력받기
n, m = map(int, input().split())
# N개의 화폐 단위 정보를 입력받기
array = []
for i in range(n):
    array.append(int(input()))

# 한 번 계산된 결과를 저장하기 위해 DP 테이블 초기화
d = [10001] * (m + 1)

# 다이나믹 프로그래밍 진행
d[0] = 0
for i in range(n):
    for j in range(array[i], m + 1):
        if d[j - array[i]] != 10001: # (i - k)원을 만드는 방법이 존재하는 경우
            a[j] = min(d[j], d[j - array[i]] + 1)

# 계산된 결과 출력
if d[m] == 10001:
    print(-1)
else:
    print(d[m])

🐢 현재 공부하고 있는 이것이 취업을 위한 코딩 테스트다 with 파이썬 - 나동빈 저자 의 책을 학습하며 기록 및 정리를 하기위한 내용들입니다. 🐢

감사합니다.😊

Algorithm 카테고리 내 다른 글 보러가기

댓글남기기