[JAVA] 17825 - 주사위 윷놀이 💛2: 한땀한땀 만들어야하는 시뮬레이션 문제(집중하자...)

2026. 4. 7. 16:58·Problem Solve

들어가며

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

사진과 같은 맵에서 4개의 말이 움직일 수 있다. 각 말이 도착한 곳의 점수를 획득한다. 종착점을 제외한 모든 곳에서는 말 중첩은 안된다. 10번의 주사위 값이 주어졌을 때 얻을 수 있는 최댓값을 정답으로 출력하면 되는 문제이다.

본문으로

이 문제,,, 완전탐색인 것은 알겠는데,,, 처음부터 매우 막막한 문제였다. 특히 그림의 map을 구현하는 것이 매우 어려웠다. 그리고 특히 중간으로 들어갈 수 있는 10, 20, 30에 해당하는 부분을 처리하기가 매우 난감했다.
처음에는 값 자체를 인덱스로 하는 인접리스트를 구성해서 만약 갈 곳이 2군데 이상이라면, 중간으로 들어갈지 말지를 생각했는데... 값이 인덱스가 되어버리니 중간에 있는 값들이 겹쳤다... 또 초반설계 잘못해서 시뮬레이션의 굴레에 빠져들었다..


이 문제는 각 노드별로 인덱스를 따로 설정해줘야하는 문제이다. 그렇치 않으면 구현자체가 너무너무 힘들어진다. 그래서 아래 로직과 같이 각 칸의 점수(score) 다음칸(next) 안으로 들어가는 파란색 화살표인지(blue)를 담는 전역배열을 각각 선언해서, 정말 한땀한땀 인덱싱을 해주었다.

public class Main {

    static final int FINISH = 32;

    static int[] dice = new int[10];
    static int[] score = new int[33]; // 각 칸의 점수
    static int[] next = new int[33];  // 빨간 화살표 기준 다음 칸
    static int[] blue = new int[33];  // 파란 화살표 시작 칸이면 그 다음 칸
    static int answer = 0;

    static void initBoard() {
        // 0 : 시작
        // 1 ~ 20 : 바깥길 (2,4,6,...,40)
        // 21 ~ 23 : 10에서 파란길 (13,16,19)
        // 24 ~ 25 : 20에서 파란길 (22,24)
        // 26 ~ 28 : 30에서 파란길 (28,27,26)
        // 29 ~ 31 : 공통길 (25,30,35)
        // 32 : 도착

        // 시작, 도착
        score[0] = 0;
        score[FINISH] = 0;

        // 바깥길 점수 세팅
        for (int i = 1; i <= 20; i++) {
            score[i] = i * 2; // 2,4,6,...,40
        }

        // 바깥길 next
        for (int i = 0; i < 20; i++) {
            next[i] = i + 1;
        }
        next[20] = FINISH; // 40 다음은 도착

        // 10에서 파란길: 13 -> 16 -> 19 -> 25
        score[21] = 13;
        score[22] = 16;
        score[23] = 19;
        next[21] = 22;
        next[22] = 23;
        next[23] = 29;

        // 20에서 파란길: 22 -> 24 -> 25
        score[24] = 22;
        score[25] = 24;
        next[24] = 25;
        next[25] = 29;

        // 30에서 파란길: 28 -> 27 -> 26 -> 25
        score[26] = 28;
        score[27] = 27;
        score[28] = 26;
        next[26] = 27;
        next[27] = 28;
        next[28] = 29;

        // 공통길: 25 -> 30 -> 35 -> 40 -> 도착
        score[29] = 25;
        score[30] = 30;
        score[31] = 35;
        next[29] = 30;
        next[30] = 31;
        next[31] = 20; // 공통길의 35 다음은 바깥길의 40(노드 20)

        // 파란 화살표
        blue[5] = 21;   // 바깥길 10에서 출발하면 13으로
        blue[10] = 24;  // 바깥길 20에서 출발하면 22로
        blue[15] = 26;  // 바깥길 30에서 출발하면 28로
    }

    static int move(int start, int dist) {
        if (start == FINISH) {
            return FINISH;
        }

        int cur = start;

        // 첫 한 칸만 파란 화살표 가능
        if (blue[cur] != 0) {
            cur = blue[cur];
            dist--;
        } else {
            cur = next[cur];
            dist--;
        }

        // 나머지는 빨간 화살표만 따라감
        while (dist > 0 && cur != FINISH) {
            cur = next[cur];
            dist--;
        }

        return cur;
    }

    static void dfs(int turn, int[] horse, int sum) {
        if (turn == 10) {
            answer = Math.max(answer, sum);
            return;
        }

        int dist = dice[turn];

        for (int i = 0; i < 4; i++) {
            int cur = horse[i];

            // 이미 도착한 말은 못 움직임
            if (cur == FINISH) {
                continue;
            }

            int nxt = move(cur, dist);

            // 도착 칸이 아니면 다른 말과 겹치면 안 됨
            if (nxt != FINISH) {
                boolean conflict = false;
                for (int j = 0; j < 4; j++) {
                    if (i == j) {
                        continue;
                    }
                    if (horse[j] == nxt) {
                        conflict = true;
                        break;
                    }
                }
                if (conflict) {
                    continue;
                }
            }

            horse[i] = nxt;
            dfs(turn + 1, horse, sum + score[nxt]);
            horse[i] = cur; // 백트래킹
        }
    }

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        for (int i = 0; i < 10; i++) {
            dice[i] = Integer.parseInt(st.nextToken());
        }

        initBoard();

        int[] horse = new int[4]; // 말 4개 모두 시작 칸(0)
        dfs(0, horse, 0);

        System.out.println(answer);
    }
}


맞긴 했지만, 인접리스트를 활용하면 조금 더 깔끔하게 모델링 할 수 있을 것 같다. 인덱싱을 따로 하되, 인접리스트를 활용하면 시작부분에서 길이 2개 이상이면 안쪽으로 들어가게 하는 로직을 손쉽게 유도할 수 있는 것 같다. 아래는 인접리스트를 활용한 풀이이다.

public class Main {

    static final int FINISH = 100;

    static int[] dice = new int[10];
    static int[] horse = new int[4];
    static int[] score = new int[101];
    static ArrayList<Integer>[] adj = new ArrayList[101];

    static void add(int from, int to) {
        adj[from].add(to);
    }

    static void setMap() {
        for (int i = 0; i <= 100; i++) {
            adj[i] = new ArrayList<>();
        }

        // 바깥길: 0 -> 1 -> 2 -> ... -> 20 -> FINISH
        for (int i = 0; i <= 19; i++) {
            add(i, i + 1);
        }
        add(20, FINISH);

        // 10에서 파란길: 5 -> 21 -> 22 -> 23 -> 24
        add(5, 21);
        add(21, 22);
        add(22, 23);
        add(23, 24);

        // 20에서 파란길: 10 -> 27 -> 28 -> 24
        add(10, 27);
        add(27, 28);
        add(28, 24);

        // 30에서 파란길: 15 -> 29 -> 30 -> 31 -> 24
        add(15, 29);
        add(29, 30);
        add(30, 31);
        add(31, 24);

        // 공통길: 24 -> 25 -> 26 -> 20 -> FINISH
        add(24, 25);
        add(25, 26);
        add(26, 20);

        // 점수 세팅
        score[1] = 2;
        score[2] = 4;
        score[3] = 6;
        score[4] = 8;
        score[5] = 10;
        score[6] = 12;
        score[7] = 14;
        score[8] = 16;
        score[9] = 18;
        score[10] = 20;
        score[11] = 22;
        score[12] = 24;
        score[13] = 26;
        score[14] = 28;
        score[15] = 30;
        score[16] = 32;
        score[17] = 34;
        score[18] = 36;
        score[19] = 38;
        score[20] = 40;

        score[21] = 13;
        score[22] = 16;
        score[23] = 19;
        score[24] = 25;
        score[25] = 30;
        score[26] = 35;

        score[27] = 22;
        score[28] = 24;

        score[29] = 28;
        score[30] = 27;
        score[31] = 26;

        score[FINISH] = 0;
    }

    static int move(int start, int dist) {
        if (start == FINISH) {
            return FINISH;
        }

        int cur = start;

        // 첫 한 칸만 파란길 가능
        if (adj[cur].size() >= 2) {
            cur = adj[cur].get(1);
            dist--;
        } else {
            cur = adj[cur].get(0);
            dist--;
        }

        // 이후는 빨간길만
        while (dist > 0 && cur != FINISH) {
            cur = adj[cur].get(0);
            dist--;
        }

        return cur;
    }

    static boolean isOccupied(int nxt, int idx) {
        if (nxt == FINISH) {
            return false;
        }

        for (int i = 0; i < 4; i++) {
            if (i == idx) continue;
            if (horse[i] == nxt) return true;
        }
        return false;
    }

    static int dfs(int turn) {
        if (turn == 10) {
            return 0;
        }

        int ret = 0;

        for (int i = 0; i < 4; i++) {
            int cur = horse[i];
            int nxt = move(cur, dice[turn]);

            if (isOccupied(nxt, i)) continue;

            horse[i] = nxt;
            ret = Math.max(ret, score[nxt] + dfs(turn + 1));
            horse[i] = cur;
        }

        return ret;
    }

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        for (int i = 0; i < 10; i++) {
            dice[i] = Integer.parseInt(st.nextToken());
        }

        setMap();
        System.out.println(dfs(0));
    }
}

마무리하며

하~~ 정말 시뮬레이션 문제 집중 안하고 풀면 실수하기가 쉬운 것 같다. 이 또한 나의 실력과 연습이 부족한 탓이겠지.... 특히 값을 인덱스로 할지 말지를 정하는 것은 한발짝 물러나 판단을 할 필요가 있는 것 같다. 간단하게 가려다가, 망했다..

 

그리고 이 문제는 30분 생각하다가 3시간짜리 수업듣고 또 30분 생각하고 할일이 생겨서 다음날 풀었는데,,, 너무 질질 끌린 것 같다. 시뮬레이션 문제는 꼭 시간재고 한번에 푸는 연습을 하자! 파이팅 🔥

'Problem Solve' 카테고리의 다른 글

[JAVA] 15685 - 드래곤 커브 💛3: 기하 문제라면 당황하지 말고 규칙을 찾아보자.  (0) 2026.04.14
[JAVA] 17143 - 낚시왕 💛1 : 영감을 주는 시뮬레이션 문제 (왕복은 실제로 이동하는 것이 아니다!)  (0) 2026.04.09
[JAVA] 15662 - 톱니바퀴 (2) 💛5: 역할을 확실하게 나누자..! - 시뮬레이션 정복기  (0) 2026.04.01
[JAVA] 17406 - 배열 돌리기 4 💛4 : 복잡한 2차원 배열 문제는 복잡도를 줄이기 위해 1차원으로 뽑자!  (0) 2026.03.31
[JAVA] 3190 - 뱀 💛4 : 시뮬레이션.. 시뮬레이션.. 시뮬레이션..!!!  (0) 2026.03.30
'Problem Solve' 카테고리의 다른 글
  • [JAVA] 15685 - 드래곤 커브 💛3: 기하 문제라면 당황하지 말고 규칙을 찾아보자.
  • [JAVA] 17143 - 낚시왕 💛1 : 영감을 주는 시뮬레이션 문제 (왕복은 실제로 이동하는 것이 아니다!)
  • [JAVA] 15662 - 톱니바퀴 (2) 💛5: 역할을 확실하게 나누자..! - 시뮬레이션 정복기
  • [JAVA] 17406 - 배열 돌리기 4 💛4 : 복잡한 2차원 배열 문제는 복잡도를 줄이기 위해 1차원으로 뽑자!
노을을
노을을
진인사대천명
  • 노을을
    노을의 개발일기장
    노을을
  • 전체
    오늘
    어제
    • All (105) N
      • Java & Kotlin (16)
      • Problem Solve (46)
      • Spring (4)
      • Infra (3) N
      • DB (2)
      • Project (2)
        • SOMA (1)
        • OJik (1)
      • OpenSource (3)
      • Various Dev (23)
        • 우아한테크코스 (9)
        • Git&Github (2)
        • Unity (12)
      • Book (3)
      • Writing (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • github
  • 공지사항

  • 인기 글

  • 태그

    contribute
    유니티
    개발
    8기
    오픈소스
    합격
    자바
    게임개발
    github
    코딩
    프리코스
    백준
    java
    비트마스킹
    트러블슈팅
    개발자
    코테
    알고리즘
    우아한테크코스
    시뮬레이션
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
노을을
[JAVA] 17825 - 주사위 윷놀이 💛2: 한땀한땀 만들어야하는 시뮬레이션 문제(집중하자...)
상단으로

티스토리툴바