🔗 11066번 : 파일 합치기
● Java 풀이
● 시간 제한 2초
● 메모리 제한 256 MB
문제

 

소설가인 김대전은 소설을 여러 장(chapter)으로 나누어 쓰는데, 각 장은 각각 다른 파일에 저장하곤 한다. 소설의 모든 장을 쓰고 나서는 각 장이 쓰여진 파일을 합쳐서 최종적으로 소설의 완성본이 들어있는 한 개의 파일을 만든다. 이 과정에서 두 개의 파일을 합쳐서 하나의 임시파일을 만들고, 이 임시파일이나 원래의 파일을 계속 두 개씩 합쳐서 소설의 여러 장들이 연속이 되도록 파일을 합쳐나가고, 최종적으로는 하나의 파일로 합친다. 두 개의 파일을 합칠 때 필요한 비용(시간 등)이 두 파일 크기의 합이라고 가정할 때, 최종적인 한 개의 파일을 완성하는데 필요한 비용의 총 합을 계산하시오.

예를 들어, C1, C2, C3, C4가 연속적인 네 개의 장을 수록하고 있는 파일이고, 파일 크기가 각각 40, 30, 30, 50 이라고 하자. 이 파일들을 합치는 과정에서, 먼저 C2와 C3를 합쳐서 임시파일 X1을 만든다. 이때 비용 60이 필요하다. 그 다음으로 C1과 X1을 합쳐 임시파일 X2를 만들면 비용 100이 필요하다. 최종적으로 X2와 C4를 합쳐 최종파일을 만들면 비용 150이 필요하다. 따라서, 최종의 한 파일을 만드는데 필요한 비용의 합은 60+100+150=310 이다. 다른 방법으로 파일을 합치면 비용을 줄일 수 있다. 먼저 C1과 C2를 합쳐 임시파일 Y1을 만들고, C3와 C4를 합쳐 임시파일 Y2를 만들고, 최종적으로 Y1과 Y2를 합쳐 최종파일을 만들 수 있다. 이때 필요한 총 비용은 70+80+150=300 이다.

소설의 각 장들이 수록되어 있는 파일의 크기가 주어졌을 때, 이 파일들을 하나의 파일로 합칠 때 필요한 최소비용을 계산하는 프로그램을 작성하시오.

 

풀이
import java.io.*;
import java.util.*;

public class Main {
    private static final int MAX_COUNT = 500;
    private static int K; // 소설 장의 수
    private static long[] files;  // 각 파일의 크기
    private static long[][] dp;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringBuilder sb = new StringBuilder();
        StringTokenizer st;
        int T = Integer.parseInt(br.readLine());
        files = new long[MAX_COUNT + 2];
        dp = new long[MAX_COUNT + 2][MAX_COUNT + 2];

        for (int tc = 0; tc < T; tc++) {
            K = Integer.parseInt(br.readLine());
            st = new StringTokenizer(br.readLine());
            for (int i = 1; i <= K; i++) {
                files[i] = files[i - 1] + Long.parseLong(st.nextToken());  // 파일 크기 배열 초기화
                Arrays.fill(dp[i], Long.MAX_VALUE);  // DP 배열 초기화
                dp[i][i] = 0;  // 자기 자신의 시간 비용 0
            }
            sb.append(findMinFileSize()).append("\n");
        }

        bw.append(sb.toString());
        bw.flush();
        bw.close();
        br.close();
    }

    private static long findMinFileSize() {
        for (int i = 1; i <= K; i++) {  // 까지
            for (int j = i - 1; 1 <= j; j--) {  // 에서
                //System.out.println("C" + j + "에서 C" + i + "까지");
                if(i - j == 1) {  // 하나 차이가 날 경우
                    dp[j][i] = Math.min(files[i] - files[j - 1], dp[j][i]);  // 시간 비용은 하나 뿐
                    continue;
                }
                for (int k = j; k < i; k++) {  // x(j, k) 파일과 x(k + 1, i) 파일 합치기
                    //System.out.println("x(" + j + ", " + k + ") 파일과 " + "x(" + (k + 1) + ", " + i + ") 파일 합치기...");
                    dp[j][i] = Math.min(dp[j][k] + dp[k + 1][i] + files[k] - files[j - 1] + files[i] - files[k], dp[j][i]);
                }
            }
        }

        return dp[1][K];
    }
}

 

어제 푼 행렬 곱셈 순서와 비슷한 DP 문제.

차이점은 경우의 수가 줄어든 것?

 

나도 헤맸고, 다른 사람들도 쉽게 헤맨 유의할 점은

  1. 연속된 파일만을 합칠 수 있다. 즉, C1와 C3 같은 경우는 합치지 못한다는 것.
  2. 총 비용 수와 파일의 크기를 같다고 보면 안 된다.

나 같은 경우에는 2번에서 엄청나게 헤매었다.

예를 들어 문제에서 주어진 테스트 케이스가 존재한다고 가정했을 때, 계산 과정은

40 30 30 50

 

(30 크기 파일) + (30 크기 파일) = 현재까지 총 시간 비용 60

40 (60) 50

 

(40 크기 파일) + (60 크기 파일) = 현재까지 총 시간 비용 100 + 60 = 160

(100) 50

 

(100 크기 파일) + (50 크기 파일) = 현재까지 총 시간 비용 160 + 50 = 310

 

그러니까, 40 크기 파일과 60 크기 파일을 합치는 과정에서 DP[40][60] 160이 되지만,

fileSize[40][60] 값은 100이라는 것이다.

무턱대고 파일 사이즈와 DP 값을 같은 값이라 판단한다면, 두 번째 테스트 케이스에서 1592라는 터무니 없이 큰 값이 나올 것이다.

(저도 알고싶지 않았어요)

 

나 같은 경우에는 파일 크기를 저장할 때 애초에 누적합으로 저장해 범위의 합을 구하는 방식으로 계산했다.

그리고 혹시 몰라서 새로운 배열 객체는 테스트 케이스 반복문 바깥에서 최대값으로 설정해주었다.

유영웅