[이코테] 7.1 다이나믹 프로그래밍 실전문제

1로 만들기


  • 5로 나누기, 3으로 나누기, 2로 나누기, 1로 빼기 의 연산을 사용해서 가장 적은 횟수로 1을 만들기

해당 문제는 잘 알려진 다이나믹 프로그래밍 문제이다. 해당 문제가 다이나믹 프로그래밍 이유는, 해당 문제를 재귀로 구현했을 경우 함수 호출 트리를 그려보면 파악할 수 있다. 부분문제에 대한 값이 상위 결과에 영향을 미치기 때문에 다이나믹 프로그래밍으로 구현할 수 있다.

아래 코드는 바텀-업 방식으로 DP테이블을 활용하여 구현한 코드이다.

import sys

def solution(x):
    table = [0] * (x+1)
    table[2] = 1
    for i in range(3, x+1):
        table[i] = table[i-1] + 1
        if i % 5 == 0:
            table[i] = min(table[i], table[i//5] + 1)
        if i % 3 == 0:
            table[i] = min(table[i], table[i//3] + 1)
        if i % 2 == 0:
            table[i] = min(table[i], table[i//2] + 1)
    return table[x]

if __name__ == "__main__":
    X = int(sys.stdin.readline().rstrip())
    print(solution(X))

개미 전사


DP문제는 DP인지 판별하는 능력, 점화식을 도출하는 능력 2가지가 필요하다.

왼쪽부터 차례대로 식량 창고를 턴다고 가정할 경우 지금 당장 털 것인지, 안 털 것인지에 대해 최댓값을 구하면 된다. 안털게 된다면 이전 값을, 털게 된다면 전전 값에 현재값을 더하면 된다.

  • a[i] = max(a[i-1], a[i-2] + now)
import sys

def solution(n, k):
    result = [0] * n
    result[0] = k[0]
    result[1] = max(k[0], k[1])
    for i in range(2, n):
        # 지금 털 것인지, 안 털고 이전 것을 그대로 사용할 것인지
        # 아래와 같이 작성하는 방법에 익숙해지자
        result[i] = max(result[i-2] + k[i], result[i-1])
    return result[n-1]

if __name__ == "__main__":
    N = int(sys.stdin.readline().rstrip())
    K = list(map(int, sys.stdin.readline().rstrip().split()))
    print(solution(N, K))

바닥 공사


해당 문제는 DP기초 예제 문제로 타일링 문제 유형이라고 한다.

이 문제도 다른 DP문제와 다르지 않게 점화식을 도출해 낼 수 있어야 한다.

  • a[i] = a[i-1] * 2a[i-2]

왼쪽부터 타일을 채운다고 가정했을때, 세번째, 즉 3x2 지점 부터 일반화를 시킬 수 있다. 바로 이전까지 타일을 채운 상황에서 1x2 타일을 세로로 넣는 경우, 전전 칸 까지 타일을 채운 상황에서 2x2타일 하나를 넣거나, 2x1 타일 두 개를 가로로 채워서 넣는 경우 총 2가지 이다. 이를 점화식으로 세우면 위와 같다. 해당 과정이 복잡할 뿐 그 뒤로 코드 구현은 아래와 같이 쉽다.

import sys

def solution(n):
    # 입력의 최댓값 만큼 메모리 할당
    table = [0] * 1001

    table[0] = 1
    table[1] = 3
    for i in range(2, n):
        table[i] = (table[i-1] + 2*table[i-2]) % 796796
    return table[n-1]

if __name__ == "__main__":
    N = int(sys.stdin.readline().rstrip())
    print(solution(N))

효율적인 화폐 구성


해당 문제는 앞서 풀었던 그리디와 비슷하지만 화폐 가치가 배수로 이루어져있지 않아서 그리디는 아니다. 따라서 탐색을 해야 하는데, 중복 부분 문제가 발생한다. → DP

그렇다면 이 문제를 어떻게 점화식을 도출할 수 있을까

  • a[i] = min(a[i], a[i-v] + v)

위에서 a는 DP테이블, v는 화폐 가치. 즉, v가 2일 경우, 2원을 포함하지 않은 이전 결과와 현재값의 비교를 통해 최적의 값을 구할 수 있다. 아래 코드는 바텀-업 방식을 사용한 코드이다.

import sys

def solution(n, m, values):
    table = [10001] * (m + 1)
    table[0] = 0
    for v in values:
        for i in range(v, m + 1):
            if table[i - v] != 10001:
                table[i] = min(table[i], table[i-v] + v)
    print(table)
    if table[m] == 10001:
        return -1
    return table[m]

if __name__ == "__main__":
    N, M = map(int, sys.stdin.readline().rstrip().split())
    Values = list()
    for _ in range(N):
        Values.append(int(sys.stdin.readline().rstrip()))
    print(solution(N, M, Values))