알고리즘/위상정렬

[BOJ] 백준 1005 - ACM Craft 풀이

송승현(SSH) 2023. 3. 30. 07:49

1. 문제

https://www.acmicpc.net/problem/1005

 

1005번: ACM Craft

첫째 줄에는 테스트케이스의 개수 T가 주어진다. 각 테스트 케이스는 다음과 같이 주어진다. 첫째 줄에 건물의 개수 N과 건물간의 건설순서 규칙의 총 개수 K이 주어진다. (건물의 번호는 1번부

www.acmicpc.net



2. 풀이

  • 문제에서 핵심으로 생각한 부분은 "특정 건물을 짓고 다음 건물을 지을 수 있다" 부분이다. 이는 위상정렬의 "특정 과목을 수강한 후에 다른 과목을 수강할 수 있다"와 같다고 볼 수 있다.
  • 기본적인 위상정렬과 같은 로직으로 돌아가지만 조금 다른 부분이 있다. 바로 건물을 짓는 시간에 대한 부분이다.
  • 문제의 그림에도 나와 있는 예제 입력 1번을 보면, 1번 건물과 연결된 2번, 3번 건물을 동시에 짓기 시작하여도 4번을 짓기 위해서는 2번과 3번이 완성되어야 한다.
  • 이를 다른 말로 하면 "현재 건물을 짓기 위해 걸리는 시간 = Max ("연결된 노드의 건물을 짓는데 걸리는 시간" + "현재 건물을 짓기 위해 걸리는 시간 " vs "현재 건물을 짓기 위해 걸리는 시간 ""이 된다.

  • 이해를 돕기 위해 예제 입력 1번의 2번째 테스트 케이스를 보자 
    • 2번째 테스트 케이스는 "7"번 건물을 완성하면 게임을 끝낼 수 있다.
    • 우선 진입 차수가 0인 노드 (1번 건물)를 큐에 넣고 건설 시간을 입력하고 while문을 시작한다.
    • 1번 노드와 연결된 노드는 2번과 3번이므로 Max(2를 짓는 시간, 2를 짓는 시간 + 1을 짓는 시간), Max(2를 짓는 시간, 2를 짓는 시간 + 1을 짓는 시간)을 구하여 dp배열에 갱신한다.
    • 만약 처리 중인 노드의 진입 차수를 -1 했을 때 0이라면 큐에 넣는다.
    • 이를 반복하다가 poll을 했을 때 마지막 노드를 만난다면, 현재 result 값과 dp[poll] 배열의 값 중 큰 값으로 갱신한다.



3. 소스코드

package TopologicalSort;

import java.io.*;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.StringTokenizer;


public class ACMCraft {
    static int N, K, W; // N : 건물 개수, K : 규칙 개수, W : 이기기 위해 지어야 하는 건물
    static Map<Integer, Integer> buildingMap; // 건물을 짓기 위한 비용이 저장된 맵
    static List<Integer>[] inList; // 인접리스트
    static int[] inDegree; // 진입 차수 배열
    static int result = 0; // 결과

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        int T = Integer.parseInt(br.readLine()); // 테스트케이스 수
        while (T-- > 0) {
            StringTokenizer st = new StringTokenizer(br.readLine());

            N = Integer.parseInt(st.nextToken());
            K = Integer.parseInt(st.nextToken());

            st = new StringTokenizer(br.readLine());

            // 비용을 맵에 저장
            buildingMap = new HashMap<>();
            for (int i = 1; i <= N; i++) {
                buildingMap.put(i, Integer.parseInt(st.nextToken()));
            }

            // 인접 리스트 초기화
            inList = new ArrayList[N + 1];
            for (int i = 1; i <= N; i++) {
                inList[i] = new ArrayList<>();
            }

            // 진입 차수 배열 초기화
            inDegree = new int[N + 1];

            // 인접 리스트 구성
            for (int i = 0; i < K; i++) {
                st = new StringTokenizer(br.readLine());

                int from = Integer.parseInt(st.nextToken());
                int to = Integer.parseInt(st.nextToken());

                inList[from].add(to);
                inDegree[to]++;
            }

            // 최종적으로 지어야 하는 건물 입력
            W = Integer.parseInt(br.readLine());

            // 위상정렬 함수 실행
            AcmTopologicalSort();

            // 결과를 버퍼에 출력
            bw.write(result + "\n");
            result = 0;
        }

        bw.flush();
        bw.close();
        br.close();
    }

    // 위상 정렬 함수
    private static void AcmTopologicalSort() {
        Queue<Integer> q = new ArrayDeque<>();
        int[] dp = new int[N + 1];

        // 진입 차수가 0인 노드를 모두 q에 offer
        // 이 떄 건물을 짓는 시간을 임시 배열에 대입
        for (int i = 1; i <= N; i++) {
            if (inDegree[i] == 0) {
                q.offer(i);
                dp[i] = buildingMap.get(i);
            }
        }

        while (!q.isEmpty()) {
            int poll = q.poll();

            // 꺼낸 노드가 최종적으로 필요한 건물이라면 건물을 짓는데 걸린 시간을 result에 대입
            if (poll == W) {
                result = Integer.max(result, tempTimeArr[poll]);
            }

            for (int i = 0; i < inList[poll].size(); i++) {
                int next = inList[poll].get(i);

                if (inList[poll].size() != 0) {
                    // 현재 노드(건물)를 건설하는 데 걸리는 시간 vs 이전 노드를 건설하는데 걸리는 시간 + 현재 노드(건물)를 건설하는 데 걸리는 시간
                    dp[next] = Math.max(dp[next], dp[poll] + buildingMap.get(next));
                }

                // 다음 노드의 진입 차수가 0이라면 큐에 넣는다.
                if (--inDegree[next] == 0) {
                    q.add(next);
                }
            }

        }
    }
}



4. 점수

75 / 100