import sys
input = sys.stdin.readline

T = int(input())

def divide_two(ind_X,ind_Y):
    if ind_X == ind_Y:
        return books[ind_X]
    cache_data = dp[ind_X][ind_Y]
    if cache_data != -1:
        return cache_data

    temp = float('inf')
    mid_sum = prefix_sum[ind_Y+1] - prefix_sum[ind_X]
    for k in range(ind_X,ind_Y):
        temp = min(temp,divide_two(ind_X,k)+divide_two(k+1,ind_Y)+mid_sum)
    dp[ind_X][ind_Y] = temp
    return temp

    

for _ in range(T):

    N = int(input())
    dp = [[-1]*N for _ in range(N)]
    books = list(map(int,input().split()))
    prefix_sum = [0]*(N+1)
    for i in range(1,N+1):
        prefix_sum[i] = prefix_sum[i-1] + books[i-1]
    result = float('inf')

    for i in range(N-1):
        result = min(result,divide_two(0,i)+divide_two(i+1,N-1))

    print(result)

어렵게 푼 문제이다.

 

먼저 특정 범위의 합을 구해야하기 때문에, prefix_sum을 먼저 구해준다.

 

그리고 재귀를 통해, 특정 위치로 나누었을때의 최소값을 구하는 함수를 통해 구해준다.

 

만약 0~N-1 칸이 있다고 했을때, 0~i 와 i+1~N-1을 나눠서 소분해서 구해주면 된다.

 

전체 0~N-1칸을 생각해보지 말고, 

 

작은 공간 A ~ A+2 칸이 있다고 해보자,

A   | |   A+1   | |   A+2

30  | |   40     | |    50 

 

라고 하면

 

1번 경우 : 30 // 40 + 50

 

2번 경우 : 30 + 40 // 50

 

각각을 구해보면

 

1번 경우 : 90 + 90 + 30 = 90 + 120 = 210

2번 경우 : 70 + 70 + 50 = 70 + 120 = 190

 

그러면 A~A+2칸에서 가장 적게 구하는 것은 30+40 // 50 일때이다.

 

그리고 위의 경우를 봐보면 120은 30 + 40 + 50을 다 더한거와 같고, 그 앞의 값만 바뀐다는 것을 알 수 있다.

 

이걸 식으로 표현하면 divide_two(A,ind) + divide_two(ind+1,B) + (books[A] + books[A+1] + books[A+2]) 

 

가 된다.

 

결국 정리하면 divide_two라는 함수에 들어왔을때 ind_X 와 ind_Y가 같으면 books[ind_X]의 값을 반환해주면 되고,

 

그 외의 경우에는 ind_X와 ind_Y의 사이값으로 나뉘었을때를 가정하고, 재귀를 통해 최소값을 구해주면 된다.

 

또한 이렇게 구한 값들은 변하지 않기 때문에

 

dp에 저장을 해주면 된다. dp[ind_X][ind_Y] 는 ind_X에서 ind_Y 까지의 범위를 계싼했을때 최저 값인 것이다.

 

위와 같이 구하면 파일 합치기의 최소값을 구할 수 있다.

 

하지만 이 방법은 N^3을 돌기 때문에 시간이 오래걸린다.

 

그래서 다른 사람들의 풀이를 보고 찾은 방법은 다음과 같다.

 

 

# # Knuth's Optimization
# # https://wogud6792.tistory.com/20

import sys
input = sys.stdin.readline

T = int(input())

for _ in range(T):
    N = int(input())
    books = list(map(int,input().split()))
    prefix_sum = [0]*(N+1)
    INF = float('inf')
    dp =[[INF] *N for _ in range(N)]
    min_k_store_array = [[0]*N for _ in range(N)]
    for i in range(N):
        dp[i][i] = 0
        min_k_store_array[i][i] = i
        prefix_sum[i+1] = prefix_sum[i] + books[i]
    for term in range(1,N):
        for i in range(N-term):
            j = term+i
            for k in range(min_k_store_array[i][j-1],min_k_store_array[i+1][j]+1):
                if k<N-1 and dp[i][j] > dp[i][k]+dp[k+1][j]+ prefix_sum[j+1] - prefix_sum[i]:
                    dp[i][j] = dp[i][k]+dp[k+1][j] + prefix_sum[j+1] - prefix_sum[i]
                    min_k_store_array[i][j] = k
    print(dp[0][N-1])

 https://wogud6792.tistory.com/20

 

위의 블로그에 설명이 잘 되어 있으니 참조 바란다.

 

DP에서 특정한 조건을 만족할 경우, 최적활 할 수 있는 기법이다.

 

위의 최적화 기법의 점화식을 보고 구현한 것이다.

 

최적화 기법이므로, 실행속도가 확연히 줄어든다.

 

하지만 저는 실전에서는 쓰기 힘들것 같다. 쓸려면 이 Knuth Optimization에 대해서 더 공부해야할 것 같다.

'알고리즘 > 백준' 카테고리의 다른 글

[BOJ/백준] 2075 N번째 큰 수  (0) 2021.03.12
[BOJ/백준] 2252 줄 세우기  (0) 2021.03.12
[BOJ/백준] 1300 K번째 수  (0) 2021.03.11
[BOJ/백준] 5052 전화번호 목록  (0) 2021.03.11
[BOJ/백준] 1963 소수 경로  (0) 2021.03.11

+ Recent posts