콘텐츠로 건너뛰기
» Posts » [C#] 백준 1005번 ‘ACM Craft’ 문제 풀기(위상 정렬)

[C#] 백준 1005번 ‘ACM Craft’ 문제 풀기(위상 정렬)

 

문제 해결에 사용된 개념

  • 위상정렬 : 
    방향 그래프에서 각 점들을 순서대로 나열하는 방법
    – 조건 : DAG(Directed Acyclic Graph) 그래프 방향성이 있고 사이클이 없는 그래프 
    – DFS를 이용한 방법과 진입차수(Indegree)를 이용한 2가지 방법이 있으나 ACM Craft에서는 건설 순서를 결정하기 위해 Indegree를 이용한 위상 정렬을 사용
  • 다이나믹 프로그래밍 : 
    – 작은 문제들을 해결한 결과로 큰 문제를 해결하는 방법
    – 중복된 계산을 줄이는 목적으로 사용됨 
    – ACM Craft에서는 각 도시의 건설 비용을 누적 하는데 사용됨

  • 큐(Queue)
    – FIFO(First In First Out) 자료 구조로 위상정렬을 구현하는데 사용
    – 도시의 indegree가 0인 도시부터 처리하기 위해 큐 사용

사용된 매서드 및 시퀀스

  • 위상정렬 시퀀스 
    – 인접 리스트 형식으로된 그래프에서 각 노드별 진입차수(indegree) 기록
    – 큐 2개 생성 (탐색큐, 결과큐)
    – 탐색큐에서 노드를 하나씩 poll하여 결과큐에 삽입 => 진입차수(indegree)가 0 이되면 정렬이 완료 된 것
    – 탐색큐에서 poll한 노드와 연결된 노드들의 진입차수(indegree)를 -1해줌
    – 진입차수 수정 후 0이 된다면 다시 탐색을 위해 탐색큐에 push
    – 위 과정을 반

  • void Input()  
    – 건물 개수와 규칙 개수를 ReadLine()
    – 각 배열을 설정함
  • int Output()
    – Queue<int>를 사용해서 위상 정렬 수행 (FIFO)
    – indegree가 0인 도시부터 시작하여, 연결된 도시들은 indegree를 감소 시키고 0이 되면 큐에 추가
    – 각 도시의 최대 건설 비용 갱신

 

class ACM
{
    //https://velog.io/@cpsn6237/3-BOJ-1005-ACM-Craft-C
    private int n, k; // 총 도시, 총 규칙 개수 
    private int targetCity; // 도착 도시

    private int[] cityCost; // 각 도시 건설 시간
    private int[] dpCost; // 각 도시의 효율적인 건설 시간
    private int[] indegree; // 도시의 필요한 규칙 개수
    private List<int>[] nextCities; // 도시의 다음 간선(목록)
    private List<int>[] prevCities; // 도시의 이전 간선(목록)

    public void Input()
    {
        // 2. 건물개수 N , 규칙개수 K
        int[] input = Array.ConvertAll(Console.ReadLine().Split(), int.Parse);
        int N = input[0];
        int K = input[1];

        // 3. 배열 사이즈 정의
        cityCost = new int[N + 1];
        dpCost = new int[N + 1];
        indegree = new int[N + 1];
        nextCities = new List<int>[N + 1];
        prevCities = new List<int>[N + 1];

        // 4. 각 도시의 다음/이전 간선 리스트 초기화
        for (int i = 1; i <= n; i++)
        {
            nextCities[i] = new List<int>();
            prevCities[i] = new List<int>();
        }

        // 5. 각 도시의 건설 시간 입력받아 저장
        var costInput = Array.ConvertAll(Console.ReadLine()!.Split(), int.Parse);
        for (int i = 1; i <= n; i++)
        {
            cityCost[i] = costInput[i - 1];
        }

        // 6. 각 간선 정보 입력받아 nextCities, prevCities, indegree 배열에 반영
        for (int i = 0; i < k; i++)
        {
            var edgeInput = Array.ConvertAll(Console.ReadLine()!.Split(), int.Parse);
            nextCities[edgeInput[0]].Add(edgeInput[1]);
            prevCities[edgeInput[1]].Add(edgeInput[0]);
            indegree[edgeInput[1]]++;
        }

        // 7. 목표 도착 도시 입력받기
        targetCity = int.Parse(Console.ReadLine()!);
    }

    public int Output()
    {
        var q = new Queue<int>();
        // indegree가 0인 도시를 큐에 추가
        for (int i = 1; i <= n; i++)
        {
            if (indegree[i] == 0)
                q.Enqueue(i);
        }

        // 큐가 빌 때까지 반복
        while (q.Count > 0)
        {
            var curCity = q.Dequeue();
            // 이전 도시 중 가장 건설이 오래걸렸던 누적 비용 계산
            var maxDP = 0;
            foreach (int node in prevCities[curCity])
            {
                maxDP = Math.Max(maxDP, dpCost[node]);
            }
            dpCost[curCity] = maxDP + cityCost[curCity];

            // 다음 도시의 indegree를 감소시키고, indegree가 0이 되면 큐에 추가
            foreach (var next in nextCities[curCity])
            {
                indegree[next]--;
                if (indegree[next] == 0)
                {
                    q.Enqueue(next);
                }
            }
        }
        // 목표 도착 도시의 누적 건설 비용 반환
        return dpCost[targetCity];
    }
}
class Program
{
    static void Main(string[] args)
    {
        // 1. 테스트 케이스
        int testcase = int.Parse(Console.ReadLine());

        while (testcase > 0)
        {
            var acm = new ACM();
            acm.Input();

            var result = acm.Output();
            Console.WriteLine(result);
        }
    }
}