백준 - 15900번(나무 탈출)

최지홍·2022년 3월 18일
0

백준

목록 보기
98/145

문제 출처: https://www.acmicpc.net/problem/15900


문제

  • 평소에 사이가 좋지 않던 성원이와 형석이가 드디어 제대로 한 판 붙으려고 한다. 성원이와 형석이 둘과 모두 똑같이 친한 인섭이가 대결 종목을 정해 가져왔다. 바로 '나무 탈출' 이라는 보드게임이다.

  • '나무 탈출' 은 N개의 정점이 있는 트리 모양으로 생긴 게임판과 몇 개의 게임말로 이루어진다. 트리의 각 정점에는 1번부터 N번까지 번호가 붙어있다. 1번 정점은 '루트 노드' 라고 불리며, 이 루트 노드를 중심으로 정점 간에 부모-자식 관계가 만들어진다. 자식이 없는 노드는 '리프 노드' 라고 불린다.

  • 이 게임은 두 사람이 번갈아 가면서 게임판에 놓여있는 게임말을 움직이는 게임이다. 처음에는 트리의 모든 리프 노드에 게임말이 하나씩 놓여있는 채로 시작한다. 어떤 사람의 차례가 오면, 그 사람은 현재 존재하는 게임말 중 아무거나 하나를 골라 그 말이 놓여있던 노드의 부모 노드로 옮긴다. 이 과정에서 한 노드에 여러 개의 게임말이 놓이게 될 수도 있다. 이렇게 옮긴 후에 만약 그 게임말이 루트 노드에 도착했다면 그 게임말을 즉시 제거한다. 모든 과정을 마치면 다음 사람에게 차례를 넘긴다. 이런 식으로 계속 진행하다가 게임말이 게임판에 존재하지 않아 고를 수 없는 사람이 지게 된다.

  • 성원이를 얕본 형석이는 쿨하게 이 게임의 선을 성원이에게 줘버렸다. 따라서 성원이가 먼저 시작하고 형석이가 나중에 시작한다. 그동안 형석이와 대결을 하면 매번 지기만 했던 성원이는 마음속에 분노가 가득 쌓였다. 이번 대결에서는 반드시 이겨서 형석이의 코를 꺾어주고 싶다. 그래서 게임을 시작하기 전에 게임판의 모양만 보고 이 게임을 이길 수 있을지 미리 알아보고 싶어졌다. 성원이가 이 게임을 이길 수 있을지 없을지를 알려주는 프로그램을 만들어 성원이를 도와주자.


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

public class Main {

    private static List<Integer>[] adjList;
    private static boolean[] visited;
    private static int result;

    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(reader.readLine()); // 정점 개수. 간선의 수는 N - 1개(이게 홀수, 리프가 홀수가 돼야된다)
        adjList = new List[N + 1]; // 1-based index
        visited = new boolean[N + 1];
        for (int i = 1; i <= N; i++) {
            adjList[i] = new ArrayList<>();
        }

        for (int i = 0; i < N - 1; i++) {
            StringTokenizer tokenizer = new StringTokenizer(reader.readLine());
            int x = Integer.parseInt(tokenizer.nextToken());
            int y = Integer.parseInt(tokenizer.nextToken());

            adjList[x].add(y);
            adjList[y].add(x);
        }

        dfs(1, 0);

        System.out.println(result % 2 == 0 ? "No" : "Yes");
    }

    private static void dfs(int num, int sum) {
        visited[num] = true; // 방문 처리

        for (int i = 0; i < adjList[num].size(); i++) {
            if (!visited[adjList[num].get(i)]) {
                visited[adjList[num].get(i)] = true;
                dfs(adjList[num].get(i), sum + 1);
            }
        }

        // dfs를 마친 후 리프 노드일 때만 값을 누적해준다. 리프 노드 -> 인접 리스트의 크기가 1(부모 정보만 가지고 있기 때문에)
        if (adjList[num].size() == 1) result += sum;
    }

}

  • 처음부터 완전히 잘못 접근한 문제다.
  • 로직을 잘못 설계하여 시간은 시간대로 걸리고 결국 풀지 못하여 검색하였다.
  • 사실 로직은 간단하다. 성원이는 홀수번에 움직이므로 홀수번에 끝이 나면 성원이가 이긴다. 즉, 트리에서 모든 리프 노드까지의 합이 홀수가 되면 전체 게임 수가 홀수가 되므로 성원이가 이기게 된다.
  • 인접 리스트를 몇번 사용하여 잘 안다고 생각하였는데 오랜만에 푸니 잘 떠오르지 않았다. 인접 리스트를 만들고 이를 통해 dfs를 수행하면서 리프 노드에 도달할 경우 값을 누적시켜 전체 값을 구할 수 있었다.
profile
백엔드 개발자가 되자!

0개의 댓글