[BOJ] 11066 파일 합치기

DP


개인적으로 동적 프로그래밍은 점화식을 세우는게 제일 중요하고 어려운 것 같다.

처음에 이 문제는 “연속적” 이라는 말을 보지 못하고 그리디, 우선순위 큐를 이용하여 해결할 수 있을 줄 알았다.

하지만 순서를 지켜야 하는 문제이므로 동적 프로그래밍 문제임을 깨달을 수 있었다.

우선 점화식을 세우기 위해서는 부분 문제로 어떻게 나눌 수 있을지를 고민해야 한다. 1~N 까지를 해결해야 하는 솔루션으로 본다면 이를 i~j까지로 트리 구조로 나누면서 다음과 같이 생각해봐야 한다.

boj11066_1.jpeg

어느 두 파일을 더해야 최소 비용이 나올지 모르기 때문에 다음과 같이 계산해야한다. 여기서 sub problem에서 연산한 비용을 더하면서 올라와야 하기 때문에 파일의 비용의 부분합을 더하여 점화식을 완성한다.

f(i, j) = min( f(i, mid) + f(mid + 1, j) + acc(i ~ j) )

이 문제는 점화식을 세워도 코드로 옮기기도 꽤 어렵다는 문제가 있다. 점화식 대로 코드를 구현하려면 다음과 같이 부분합의 길이를 offset으로 두고 대각선으로 탐색하는 방향으로 구현해야 한다.

boj11066_2.jpeg

i 는 대각선을 반복하는 iteration 을 의미하고 offset은 i로부터 대각선으로 얼만큼 떨어져 있는지를 의미한다. 즉 부분합의 길이이다. 그리고 mid는 0~부분합의 길이로 중간에 부분합을 자르는 token 역할을 한다.

이를 염두해서 구현한 코드는 아래와 같다.

구현


import sys

def dp(k):
    # 초기 설정
    table = [[0]*(k+1) for _ in range(k+1)]
    acc = [0] * (k+1)
    for i in range(1, k+1):
        acc[i] = acc[i-1] + files[i-1]

    for offset in range(1, k+1):
        for i in range(1, k - offset + 1):
            x, y = i, i+offset
            table[x][y] = INF
            for mid in range(offset):
                table[x][y] = min(table[x][y], (table[x][x+mid] + table[x+1+mid][y] + acc[y] - acc[x-1]))
    return table[1][k]

if __name__ == "__main__":
    T = int(sys.stdin.readline().rstrip())
    INF = int(1e9)
    for _ in range(T):
        K = int(sys.stdin.readline().rstrip())
        files = list(map(int, sys.stdin.readline().rstrip().split()))
        print(dp(K))