5 minute read

1로 만들기

정수가 X가 주어질 때 정수 X에 사용할 수 있는 연산은 다음 4가지 이다.

  • X가 5로 나누어 떨어지면, 5로 나눈다.
  • X가 3으로 나누어 떨어지면, 3으로 나눈다.
  • X가 2로 나누어 떨어지면, 2로 나눈다.
  • X에서 1을 뺀다.

정수 X가 주어졌을 때, 연산 4개를 적절히 사용해서 1을 만들려고 한다. 연산을 사용하는 횟수의 최솟값을 출력하시오.

문제

  • 첫째 줄에 정수 X가 주어진다.(1 <= X <= 30,000)

접근 방법

다이나믹 프로그래밍 문제이다. 그림으로 이해하면 좋은데, 다음은 X=6일 때, 함수가 호출되는 과정이다.


해당 내용을 점화식으로 표현하면 다음 식과 같고, 점화식 끝에 1을 더해주는 이유는 함수의 호출 횟수를 구해야 하기 때문이다. 파이썬의 min()함수를 사용하면 편하다.
\(a_i = min(a_{i-1},a_{i/2},a_{i/3},a_{i/5}) + 1\)

코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 정수 X를 입력 받기
x = int(input())

# 앞서 계산된 결과를 저장하기 위한 DP 테이블 초기화
d = [0] * 1000001

# 다이나믹 프로그래밍(Dynamic Programming) 진행(보텀업)
for i in range(2, x + 1):
    # 현재의 수에서 1을 빼는 경우
    d[i] = d[i - 1] + 1
    # 현재의 수가 2로 나누어 떨어지는 경우
    if i % 2 == 0:
        d[i] = min(d[i], d[i // 2] + 1)
    # 현재의 수가 3으로 나누어 떨어지는 경우
    if i % 3 == 0:
        d[i] = min(d[i], d[i // 3] + 1)
    # 현재의 수가 5로 나누어 떨어지는 경우
    if i % 5 == 0:
        d[i] = min(d[i], d[i // 5] + 1)

print(d[x])
1
2
26
3

개미전사

개미 전사는 부족한 식량을 충당하고자 메뚜기 마을의 식량창고를 몰래 공격하려고 한다. 여러 개의 식량창고는 일직선으로 이어져 있다. 각 식량 창고에는 정해진 수의 식량을 저장하고 있으며 개미 전사는 식량창고를 선택적으로 약탈하여 식량을 빼앗을 예정이다. 이때 메뚜기 정찰병들은 일직선상에 존재하는 식량창고 중에서 서로 인접한 식량창고가 공격받으면 바로 알아챌 수 있다. 따라서 개미 전사는 최소한 한 칸 이상 떨어진 식량창고를 약탈해야 한다. 예를 들어 1, 3, 1, 5 의 식량창고가 있다면 개미 전사는 두 번째, 네 번째 식량창고를 털여야 최댓값인 8개의 식량을 빼앗을 수 있다.

문제

  • 첫째 줄에 식량창고의 개수 N이 주어진다. (3 <= N <= 100)
  • 둘째 줄에 공백으로 구분되어 각 식량창고에 저장된 식량의 개수 K가 주어진다. (0 <= K <= 1,000)
  • 첫째 줄에 개미 전사가 얻을 수 있는 식량의 최댓값을 출력하시오.

접근방법

왼쪽 부터 차례대로 식량창고를 턴다고 가정하고 점화식을 세우자.

  • i-1 번째 식량창고를 털면 i번째 식량창고를 털 수 없다.
  • i-2 번째 식량창고를 털면 i번째 식량창고를 털 수 있다.
  • i-3 번째 식량창고는 고려할 필요가 없다. 한칸 이상 떨어진 식량창고 항상 털 수 있기 때문이다.
    따라서 i번째 식량창고에 있는 식량의 양이 $k_i$라고 했을 때 점화식은 아래와 같다.
    $a_i = max(a_{i-1},a_{i-2}+k_i)$

코드(바텀업 방식)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 정수 N을 입력 받기
n = int(input())
# 모든 식량 정보 입력 받기
array = list(map(int, input().split()))

# 앞서 계산된 결과를 저장하기 위한 DP 테이블 초기화
d = [0] * 100

# 다이나믹 프로그래밍(Dynamic Programming) 진행 (보텀업)
d[0] = array[0]
d[1] = max(array[0], array[1]) 
for i in range(2, n):
    d[i] = max(d[i - 1], d[i - 2] + array[i])

# 계산된 결과 출력
print(d[n - 1])
1
2
3
4
1 3 1 5
8

바닥 공사

다음과 같은 바닥을 덮개로 덮으려고 한다. 바닥을 채우는 모든 경우의 수를 구하는 프로그램을 작성해라.


문제

  • 첫째 줄에 N이 주어진다. (1 <= N <= 1,000)
  • 첫째 줄에 2 x N 크기의 바닥을 채우는 방법의 수를 796,696으로 나눈 나머지를 출력한다.

접근방법

이 문제도 다이나믹 프로그래밍의 기초 예제인 타일링 문제 유형이다. 왼쪽부터 차례대로 바닥을 덮개로 채운다고 생각하면 어렵지 않게 점화식을 세울 수 있다.

  • 왼쪽부터 i-1까지 길이가 덮개로 이미 채워져 있으면 2x1 덮개를 채우는 하나의 경우밖에 존재하지 않는다.
  • 왼쪽부터 i-2까지 길이가 덮개로 이미 채워져 있으면 1x2 덮개 2개를 넣는 경우, 혹은 2x2의 덮개 하나를 넣는 경우로 2가지 경구가 존재한다.
    이 문제도 왼쪽부터 N-2 미만의 길이에 대해서는 고려할 필요가 없다. 왜냐하면 사용할 수 있는 덮개의 형태가 최대 2x2의 직사각형 형태이기 때문이다.
    $a_i = a_{i-1}+a_{i-2} * 2$

코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 정수 N을 입력 받기
n = int(input())

# 앞서 계산된 결과를 저장하기 위한 DP 테이블 초기화
d = [0] * 1001

# 다이나믹 프로그래밍(Dynamic Programming) 진행 (보텀업)
d[1] = 1
d[2] = 3
for i in range(3, n + 1):
    d[i] = (d[i - 1] + 2 * d[i - 2]) % 796796

# 계산된 결과 출력
print(d[n])
1
2
3
5

효율적인 화폐 구성

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

문제

  • 첫째 줄에 N,M이 주어진다. (1 <= N <= 100, 1 <= M <= 10,000)
  • 이후 N개의 줄에는 각 화폐의 가치가 주어진다. 화폐 가치는 10,000보다 작거나 같은 자연수이다.
  • 첫째 줄에 M원을 만들기 위한 최소한의 화폐 개수를 출력한다.
  • 불가능할 때는 -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,0001$

코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 정수 N, M을 입력 받기
n, m = map(int, input().split())
# N개의 화폐 단위 정보를 입력 받기
array = []
for i in range(n):
    array.append(int(input()))

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

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

# 계산된 결과 출력
if d[m] == 10001: # 최종적으로 M원을 만드는 방법이 없는 경우
    print(-1)
else:
    print(d[m])
1
2
3
4
2 15
2
3
5

Comments