[BOJ] 28707 - 배열 정렬

[BOJ] 28707 - 배열 정렬

백준 28707번 문제 풀이

[BOJ] 28707 - 배열 정렬

1. 문제 정리

  • 길이가 N인 배열이 주어집니다. (배열의 원소는 1 ~ 10 사잇값으로 주어집니다.)
  • 다음으로 i번째 원소와 j번째 원소를 교환했을 때 드는 비용 정보를 M개 제공해줍니다.
    • 인덱스 번호는 1 ~ N 사잇값으로 제공됩니다.
  • 배열을 오름차순으로 정렬하는 데 필요한 최소 비용을 출력하면 됩니다.
    • 단, 정렬하지 못할 때 -1을 출력합니다.

2. 문제 풀이

아이디어
  • 바로 풀이 떠오르지 않아서 우선 알 수 있는 정보를 정리했습니다.
  • (3, 2, 1) 이라는 배열이 주어지고 [2, 3, 1] 비용 정보를 준다고 했을 때
    • 저희는 (3, 2, 1)에서 (3, 1, 2) 이동 비용 1이 발생하는 것을 알 수 있습니다.
    • 반대도 마찬가지로 비용 1이 발생하는 것을 알 수 있습니다.
    • (3, 2, 1) ← 1 → (3, 1, 2)
  • 위 방식으로 예제 입력을 시각화시켰더니 그래프로 보였습니다.
  • 또한 저희는 (3, 2, 1)의 시작에서 끝 모양이 (1, 2, 3)임을 알 수 있었습니다.
  • 결국 시작 상태에 목표 상태까지 가는 최소 비용을 구하는 문제로 바뀝니다.
결론
  • 노드에서 노드의 최단 경로 문제면 간단하게 다익스트라로 해결 할수 있습니다.
  • 다른 점은 인접 상태에 대한 정보를 모르므로 매번 노드에 다음 노드로 이동할 때 비용 정보를 통하여 인접 상태를 생성해야 합니다.

3. 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.*;


class Main {

    int arrToStatus(int[] data) {
        int res = 0;

        for (int v: data) {
            res = res * 10 + v;
        }
        return res;
    }

    void statusToArr(int data, int[] res) {
        int i = res.length - 1;

        while (data > 0) {
            res[i--] = data % 10;
            data /= 10;
        }
        while (i >= 0) {
            res[i--] = 0;
        }
    }

    void solve() throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        //== 입력 받기 및 초기화

        // 데이터 정보 입력 받기.
        int N = Integer.parseInt(br.readLine());

        // -1 하는 이유: 원소의 범위를 0 ~ 9으로 설정하여 자릿수를 한 자리로 만들기 위함.
        int[] data = Arrays.stream(br.readLine().split(" "))
                .mapToInt(a -> Integer.parseInt(a) - 1).toArray();

        // 조작 정보 입력 받기.
        int M = Integer.parseInt(br.readLine());
        int[][] path = new int[M][];
        for (int i = 0; i < M; i++) {
            // -1 하는 이유: 인덱스 범위로 조정하기 위함.
            path[i] = Arrays.stream(br.readLine().split(" "))
                    .mapToInt(a -> Integer.parseInt(a) - 1).toArray();
            path[i][2] += 1; // 비용도 1일 감소 되므로 다시 1 증가.
        }

        // key: 상태, value: 비용
        HashMap<Integer, Integer> cost = new HashMap<>();

        // int[2] { 현재 상태, 현재 비용 }
        PriorityQueue<int[]> queue = 
                    new PriorityQueue<>(Comparator.comparingInt(o -> o[1]));

        // 상태 배열 : 매번 새로운 배열로 만들지 않고 재활용하기 위함.
        int[] tempArrPath = new int[N];

        //== 처리하기
        int startPath = arrToStatus(data); // 초기 상태.
        Arrays.sort(data); // 목표 상태.

        cost.put(startPath, 0);
        queue.add(new int[] {startPath, 0});

        // 다익스트라 시작.
        while (!queue.isEmpty()) {
            int[] nowStatus = queue.poll();

            if (nowStatus[1] > cost.getOrDefault(nowStatus[0], Integer.MAX_VALUE)) {
                continue;
            }

            for (int[] p: path) {
                // intPath to arrPath -> 이런 식으로 재사용.
                statusToArr(nowStatus[0], tempArrPath);

                // swap
                int tmp = tempArrPath[p[0]];
                tempArrPath[p[0]] = tempArrPath[p[1]];
                tempArrPath[p[1]] = tmp;

                // 인접 상태에 대한 비용 계산.
                int intPath = arrToStatus(tempArrPath);
                int value = nowStatus[1] + p[2];

                if (!cost.containsKey(intPath) || cost.get(intPath) > value) {
                    cost.put(intPath, value);
                    queue.add(new int[] {intPath, value});
                }
            }
        }

        System.out.println(cost.getOrDefault(arrToStatus(data), -1));
    }

    public static void main(String[] args) throws Exception {
        new Main().solve();
    }
}
This post is licensed under CC BY 4.0 by the author.