본문 바로가기

CT

[BOJ] 30단계. 그래프와 순회 2

10 1697 숨바꼭질

 

수빈이는 동생과 숨바꼭질을 하고 있다. 수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다. 

수빈이는 걷거나 순간이동을 할 수 있다.만약, 수빈이의 위치가 X일 때

  • 걷는다면 1초 후에 X-1 또는 X+1로 이동
  • 순간이동을 하는 경우에는 1초 후에 2*X의 위치로 이동

수빈이와 동생의 위치가 주어졌을 때, 수빈이가 동생을 찾을 수 있는 가장 빠른 시간이 몇 초 후인지 구하는 프로그램을 작성하시오.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    private static boolean[] visited;
    private static int[] map;

    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String[] str = reader.readLine().split(" ");
        int posA = Integer.parseInt(str[0]);
        int posB = Integer.parseInt(str[1]);
        visited = new boolean[100001];
        map = new int[100001];

        bfs(posA);
        System.out.println(map[posB]);
    }

    static void bfs(int start) {
        Queue<Integer> queue = new LinkedList<>();
        queue.add(start);
        visited[start] = true;
        while(!queue.isEmpty()) {
            int pos = queue.remove();

            // 현재 위치(pos)에서 이동 가능한 위치 탐색
            int nextPos = pos + 1;
            if(isNextPos(nextPos)) {
                queue.add(nextPos);
                visited[nextPos] = true;
                map[nextPos] = map[pos] + 1;
            }
            nextPos = pos - 1;
            if(isNextPos(nextPos)) {
                queue.add(nextPos);
                visited[nextPos] = true;
                map[nextPos] = map[pos] + 1;
            }
            nextPos = 2 * pos;
            if(isNextPos(nextPos)) {
                queue.add(nextPos);
                visited[nextPos] = true;
                map[nextPos] = map[pos] + 1;
            }
        }
    }

    private static boolean isNextPos(int pos) {
        return pos >= 0 && pos < map.length && !visited[pos] && map[pos] == 0;
    }
}

 

11 7562 나이트의 이동

 

체스판 위에 한 나이트가 놓여져 있다. 나이트가 한 번에 이동할 수 있는 칸은 아래 그림에 나와있다.

나이트가 이동하려고 하는 칸이 주어진다. 나이트는 몇 번 움직이면 이 칸으로 이동할 수 있을까?

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    private static final int[] dx = { -1, -1, -2, -2, 1, 1, 2, 2};
    private static final int[] dy = { -2, 2, -1, 1, -2, 2, -1, 1};
    private static boolean[][] visited;
    private static int[][] chessboard;
    private static int l;


    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        int T = Integer.parseInt(reader.readLine());
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < T; i++) {
            l = Integer.parseInt(reader.readLine()); // 체스판 한 변의 길이

            StringTokenizer st = new StringTokenizer(reader.readLine());
            // 현재 나이트의 위치
            int startX = Integer.parseInt(st.nextToken());
            int startY = Integer.parseInt(st.nextToken());
            st = new StringTokenizer(reader.readLine());
            // 나이트가 이동하려는 위치
            int endX = Integer.parseInt(st.nextToken());
            int endY = Integer.parseInt(st.nextToken());

            visited = new boolean[l][l];
            chessboard = new int[l][l];

            bfs(startX, startY);
            sb.append(chessboard[endX][endY]).append("\n");
        }
        System.out.print(sb);
    }

    static void bfs(int startX, int startY) {
        Queue<Integer> qX = new LinkedList<>();
        Queue<Integer> qY = new LinkedList<>();
        qX.add(startX);
        qY.add(startY);
        visited[startX][startY] = true;

        while(!qX.isEmpty() && !qY.isEmpty()) {
            int x = qX.remove();
            int y = qY.remove();

            // 현재 위치 (x,y)에서 이동 가능한 위치 (nextX, nextY) 탐색
            for(int i = 0; i < dx.length; i++) {
                int nextX = x + dx[i];
                int nextY = y + dy[i];
                if(isNextPos(nextX, nextY)) {
                    qX.add(nextX);
                    qY.add(nextY);
                    visited[nextX][nextY] = true;
                    chessboard[nextX][nextY] = chessboard[x][y] + 1;
                }
            }
        }
    }

    private static boolean isNextPos(int x, int y) {
        return x >= 0 && y >= 0 && x < l && y < l && !visited[x][y] && chessboard[x][y] == 0;
    }
}

 

  • 보기 사진 기반으로 이동 가능한 좌표 추출
  • 현재 위치에서 이동 가능한 위치를 탐색하고 chessboard 의 값을 [현재 위치의 값 + 1] 으로 누적
  • chessboard 의 값은 최초로 방문했을 때 초기화 되므로 항상 시작점으로부터 최단거리를 나타냄
12 7576 토마토

 

시작점이 여러 개인 BFS 문제

토마토를 아래의 그림과 같이 격자 모양 상자의 칸에 하나씩 넣어서 창고에 보관한다.

창고에 보관되는 토마토들 중에는 잘 익은 것도 있지만, 아직 익지 않은 토마토들도 있을 수 있다.

보관 후 하루가 지나면, 익은 토마토들의 인접한 곳에 있는 익지 않은 토마토들은 익은 토마토의 영향을 받아 익게 된다.

대각선 방향에 있는 토마토들에게는 영향을 주지 못하며, 토마토가 혼자 저절로 익는 경우는 없다고 가정한다.

 

토마토를 창고에 보관하는 격자모양의 상자들의 크기와 익은 토마토들과 익지 않은 토마토들의 정보가 주어졌을 때, 며칠이 지나면 토마토들이 모두 익는지, 그 최소 일수를 구하는 프로그램을 작성하라. 단, 상자의 일부 칸에는 토마토가 들어있지 않을 수도 있다.

 

1: 익은 토마토, 0: 익지 않은 토마토, -1: 토마토가 들어있지 않은 칸

저장될 때부터 모든 토마토가 익어있는 상태이면 0을 출력해야 하고, 토마토가 모두 익지는 못하는 상황이면 -1을 출력해야 한다.

 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    private static final int[] dx = { -1, 1, 0, 0};
    private static final int[] dy = { 0, 0, -1, 1};
    private static int[][] storage;
    private final static Queue<Integer> qX = new LinkedList<>();
    private final static Queue<Integer> qY = new LinkedList<>();
    private static int M;
    private static int N;

    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(reader.readLine());
        M = Integer.parseInt(st.nextToken());
        N = Integer.parseInt(st.nextToken());
        storage = new int[N][M];
        for(int i = 0; i < N; i++) {
            st = new StringTokenizer(reader.readLine());
            for(int j = 0; j < M; j++) {
                storage[i][j] = Integer.parseInt(st.nextToken());
                // 시작 위치(익은 토마토)를 큐에 추가
                if(storage[i][j] == 1) {
                    qX.add(i);
                    qY.add(j);
                }
            }
        }

        bfs();

        int max = 0;
        for(int i = 0; i < N; i++) {
            for(int j = 0; j < M; j++) {
                // 익지 않은 토마토가 하나라도 있다면 -1 반환
                if(storage[i][j] == 0) {
                    System.out.println(-1);
                    return;
                }
                max = Math.max(max, storage[i][j]);
            }
        }
        System.out.println(max - 1);
    }

    static void bfs() {
        while(!qX.isEmpty() && !qY.isEmpty()) {
            int x = qX.remove();
            int y = qY.remove();

            // 현재 위치 (x,y)에서 이동 가능한 위치 (nextX, nextY) 탐색
            for(int i = 0; i < dx.length; i++) {
                int nextX = x + dx[i];
                int nextY = y + dy[i];
                if(nextX >= 0 && nextY >= 0 && nextX < N && nextY < M) {
                    if(storage[nextX][nextY] == 0) {
                        storage[nextX][nextY] = storage[x][y] + 1;
                        qX.add(nextX);
                        qY.add(nextY);
                    }
                }
            }
        }
    }
}

 

  • 익은 토마토의 위치(1)를 큐에 추가하고 bfs 진행
  • 인접한 칸 중 익지 않은 토마토(0)에 대해 storage 값을 [현재 시간 + 1] 로 초기화
  • 시작 위치가 다르더라도 시작 위치로부터 떨어진 거리(뎁스)가 같다면 동일한 값로 초기화 되므로 항상 최소 값을 가짐
  • 탐색이 끝난 후에 익지 않은 토마토가 하나라도 있다면 -1 반환
13 7569 토마토

 

토마토를 아래의 그림과 같이 격자모양 상자의 칸에 하나씩 넣은 다음, 상자들을 수직으로 쌓아 올려서 창고에 보관한다.

하나의 토마토에 인접한 곳은 위, 아래, 왼쪽, 오른쪽, 앞, 뒤 여섯 방향에 있는 토마토를 의미한다.

대각선 방향에 있는 토마토들에게는 영향을 주지 못하며, 토마토가 혼자 저절로 익는 경우는 없다고 가정한다.

토마토를 창고에 보관하는 격자모양의 상자들의 크기와 익은 토마토들과 익지 않은 토마토들의 정보가 주어졌을 때, 며칠이 지나면 토마토들이 모두 익는지, 그 최소 일수를 구하는 프로그램을 작성하라.

단, 상자의 일부 칸에는 토마토가 들어있지 않을 수도 있다.

 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    private static final int[] dx = { -1, 1, 0, 0, 0, 0};
    private static final int[] dy = { 0, 0, -1, 1, 0, 0};
    private static final int[] dz = { 0, 0, 0, 0, -1, 1};
    private static final Queue<Position> queue = new LinkedList<>();
    private static int M;
    private static int N;
    private static int H;
    private static int[][][] storage;

    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(reader.readLine());
        M = Integer.parseInt(st.nextToken()); // 상자 가로 칸의 수 (y)
        N = Integer.parseInt(st.nextToken()); // 상자 세로 칸의 수 (x)
        H = Integer.parseInt(st.nextToken()); // 쌓아올려지는 상자의 수 (z)
        storage = new int[N][M][H];
        for(int i = 0; i < H; i++) {
            for(int j = 0; j < N; j++) {
                st = new StringTokenizer(reader.readLine());
                for(int k = 0; k < M; k++) {
                    storage[j][k][i] = Integer.parseInt(st.nextToken());
                    // 시작 위치(익은 토마토)를 큐에 추가
                    if(storage[j][k][i] == 1) {
                        queue.add(new Position(j, k, i));
                    }
                }
            }
        }

        bfs();

        int max = 0;
        for(int i = 0; i < H; i++) {
            for(int j = 0; j < N; j++) {
                for(int k = 0; k < M; k++) {
                    if(storage[j][k][i] == 0) {
                        System.out.println(-1);
                        return;
                    }
                    max = Math.max(max, storage[j][k][i]);
                }
            }
        }
        System.out.println(max - 1);
    }

    static void bfs() {
        while(!queue.isEmpty()) {
            Position pos = queue.remove();

            // 현재 위치 (x,y)에서 이동 가능한 위치 (nextX, nextY) 탐색
            for(int i = 0; i < dx.length; i++) {
                int nextX = pos.getX() + dx[i];
                int nextY = pos.getY() + dy[i];
                int nextZ = pos.getZ() + dz[i];
                if(nextX >= 0 && nextY >= 0 && nextZ >= 0 && nextX < N && nextY < M && nextZ < H) {
                    if(storage[nextX][nextY][nextZ] == 0) {
                        storage[nextX][nextY][nextZ] = storage[pos.getX()][pos.getY()][pos.getZ()] + 1;
                        queue.add(new Position(nextX, nextY, nextZ));
                    }
                }
            }
        }
    }

    static class Position {
        private final int x;
        private final int y;
        private final int z;

        public Position(int x, int y, int z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public int getX() {
            return x;
        }

        public int getY() {
            return y;
        }

        public int getZ() {
            return z;
        }
    }
}

 

  • 12번 문제의 3차원 버전
14 16928 뱀과 사다리 게임

 

뱀과 사다리 게임을 즐겨 하는 큐브러버는 어느 날 궁금한 점이 생겼다.

주사위를 조작해 내가 원하는 수가 나오게 만들 수 있다면, 최소 몇 번만에 도착점에 도착할 수 있을까?

게임은 정육면체 주사위를 사용하며, 주사위의 각 면에는 1부터 6까지 수가 하나씩 적혀있다.

게임은 크기가 10×10이고, 총 100개의 칸으로 나누어져 있는 보드판에서 진행된다. 보드판에는 1부터 100까지 수가 하나씩 순서대로 적혀져 있다.

플레이어는 주사위를 굴려 나온 수만큼 이동해야 한다. 만약 주사위를 굴린 결과가 100번 칸을 넘어간다면 이동할 수 없다.

도착한 칸이 사다리면, 사다리를 타고 위로 올라간다. 뱀이 있는 칸에 도착하면, 뱀을 따라서 내려가게 된다. 즉, 사다리를 이용해 이동한 칸의 번호는 원래 있던 칸의 번호보다 크고, 뱀을 이용해 이동한 칸의 번호는 원래 있던 칸의 번호보다 작아진다.

게임의 목표는 1번 칸에서 시작해서 100번 칸에 도착하는 것이다.

게임판의 상태가 주어졌을 때, 100번 칸에 도착하기 위해 주사위를 굴려야 하는 횟수의 최솟값을 구해보자.

 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    private static final int[] board = new int[100]; // 10 X 10 (100칸);
    private final static Map<Integer, Integer> map = new HashMap<>();

    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(reader.readLine());
        int N = Integer.parseInt(st.nextToken()); // 사다리 수
        int M = Integer.parseInt(st.nextToken()); // 뱀 수

        // 사다리와 뱀의 위치 저장
        for(int i = 0; i < N + M; i++) {
            st = new StringTokenizer(reader.readLine());
            int x = Integer.parseInt(st.nextToken()) - 1;
            int y = Integer.parseInt(st.nextToken()) - 1;
            map.put(x, y);
        }

        bfs(0);
        System.out.println(board[99]);
    }

    static void bfs(int start) {
        Queue<Integer> queue = new LinkedList<>();
        queue.add(start);
        while(!queue.isEmpty()) {
            int pos = queue.remove();
            // 현재 위치(pos)에서 이동 가능한 위치(nextPos) 탐색
            // 주사위에서 나올 수 있는 수: 1 ~ 6
            for(int i = 1; i <= 6; i++) {
                int nextPos = pos + i;
                if(nextPos < 0 || nextPos > 99) {
                    continue;
                }
                // 주사위로 이동한 위치에 사다리나 뱀이 있는지 확인
                if(map.containsKey(nextPos)) {
                    nextPos = map.get(nextPos);
                }
                if(board[nextPos] == 0) {
                    board[nextPos] = board[pos] + 1;
                    queue.add(nextPos);
                }
            }
        }
    }
}

 

  • 1번 ~ 100번 보드판 -> 0번 ~ 99번 인덱스로 범위를 변경하기 위해 사다리와 뱀의 위치 값에 대해 -1
  • 모든 칸은 최대 하나의 사다리 또는 뱀만 가지고 있으며, 동시에 두 가지를 모두 가지고 있는 경우가 없으므로 사다리와 뱀의 정보를 Map 자료구조에 저장 (중복 키 없음)
  • 주사위를 굴린 다음 위치(nextPos)에 대해 사다리나 뱀을 고려하여 큐에 저장
  • 마지막 위치(board[99])에 누적된 주사위 횟수 최소값을 출력
15 2206 벽 부수고 이동하기

 

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다.

(1, 1)에서 (N, M)의 위치까지 이동하려 하는데, 이때 최단 경로로 이동하려 한다. 최단경로는 맵에서 가장 적은 개수의 칸을 지나는 경로를 말하는데, 이때 시작하는 칸과 끝나는 칸도 포함해서 센다.

만약에 이동하는 도중에 한 개의 벽을 부수고 이동하는 것이 좀 더 경로가 짧아진다면, 벽을 한 개 까지 부수고 이동하여도 된다.

한 칸에서 이동할 수 있는 칸은 상하좌우로 인접한 칸이다.

맵이 주어졌을 때, 최단 경로를 구해 내는 프로그램을 작성하시오.

 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    private static final int[] dx = {-1, 1, 0, 0};
    private static final int[] dy = {0, 0, -1, 1};
    private static int[][] map;
    private static boolean[][][] visited;
    private static int N;
    private static int M;

    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(reader.readLine());
        N = Integer.parseInt(st.nextToken()); // row
        M = Integer.parseInt(st.nextToken()); // col
        map = new int[N][M];
        //cnt = new int[N][M];
        visited = new boolean[2][N][M];

        for(int i = 0; i < N; i++) {
            String input = reader.readLine();
            for(int j = 0; j < M; j++) {
                map[i][j] = input.charAt(j) - '0';
            }
        }

        bfs(0, 0);
    }

    static void bfs(int startX, int startY) {
        Queue<Position> queue = new LinkedList<>();
        queue.add(new Position(startX, startY, 1, false));
        visited[0][startX][startY] = true;
        while(!queue.isEmpty()) {
            Position pos = queue.remove();
            int x = pos.getX();
            int y = pos.getY();
            int cnt = pos.getCnt();
            boolean broke = pos.isBroke();

            if(x == N-1 && y == M-1) {
                System.out.println(cnt);
                return;
            }
            for(int i = 0; i < dx.length; i++) {
                int nextX = x + dx[i];
                int nextY = y + dy[i];
                int nextCnt = cnt + 1;
                if(nextX < 0 || nextY < 0 || nextX >= N || nextY >= M) {
                    continue;
                }

                // 다음 위치가 벽인 경우
                if(map[nextX][nextY] == 1) {
                    // 지금까지 벽을 부순 적 없고, 해당 벽을 부수고 방문한 이력이 없다면
                    if(!broke && !visited[1][nextX][nextY]) {
                        visited[1][nextX][nextY] = true;
                        queue.add(new Position(nextX, nextY, nextCnt, true));
                    }
                }
                // 다음 위치가 길일 경우
                else {
                    // 벽을 부순 적 있고, 벽을 부수고 다음 위치에 방문한 이력이 없다면
                    if(broke && !visited[1][nextX][nextY]) {
                        visited[1][nextX][nextY] = true;
                        queue.add(new Position(nextX, nextY, nextCnt, true));
                    }
                    // 벽을 부순 적 없고, 벽을 부수지 않고 다음 위치에 방문한 이력이 없다면
                    else if(!broke && !visited[0][nextX][nextY]) {
                        visited[0][nextX][nextY] = true;
                        queue.add(new Position(nextX, nextY, nextCnt, false));
                    }
                }
            }
        }
        System.out.println(-1);
    }

    static class Position {
        private final int x;
        private final int y;
        private final int cnt;
        private final boolean broke;

        public Position(int x, int y, int cnt, boolean broke) {
            this.x = x;
            this.y = y;
            this.cnt = cnt;
            this.broke = broke;
        }

        public int getX() {
            return this.x;
        }

        public int getY() {
            return this.y;
        }

        public int getCnt() {
            return this.cnt;
        }

        public boolean isBroke() {
            return this.broke;
        }
    }
}

 

  • 특정 위치에서 벽을 부수고 부수지 않고는 선택이므로 그 위치에 어떤 방법이 먼저 도달했다고 해도, 반드시 도착점에 먼저 도달하는 것은 아님
  • 2차원 배열로 방문 여부를 체크할 경우, 특정 위치에 벽을 부수고 먼저 방문했다면 벽을 부수지 않고 도달해도 이미 방문 체크가 되어 있기 때문에 방문할 수 없음 → 인접한 칸에 대해 벽을 부수고 도달했는지, 벽을 부수지 않고 도달했는지 2가지 경우로 방문 체크 ( 3차원 배열 사용 )
    • visited[0][m][n]: 벽을 부수지 않고 (m,n) 방문
    • visited[1][m][n]: 벽을 부수고 (m,n) 방문
  • 벽은 1번만 부술 수 있기 때문에 현재 위치까지 벽을 부순 적 있는지 여부(broke)는 위치를 객체(Position)로 만들어 기억하도록 함
  • 다음 위치가 벽일 때, 아래 조건 만족 시 큐에 다음 위치 추가
    1. 현재 위치까지 벽을 부순 적이 없고 (pos.broke == false)
    2. 해당 벽을 부수고 먼저 도달한 이력이 없다면 (visited[1][nx][ny] == false)
  • 다음 위치가 길이라면, 현재 위치(pos)까지 벽을 부순 적 있는지 없는지 여부에 따라 방문 체크
    • 현재 위치까지 벽을 부순 적이 없고(pos.broke == false), 다음 위치(nx,ny)에 벽을 부수지 않고 먼저 도달한 이력이 없다면(visited[0][nx][ny] == false), 큐에 다음 위치 추가
    • 현재 위치까지 벽을 부순 적이 있고(pos.broke == true), 다음 위치(nx,ny)에 벽을 부수고 먼저 도달한 이력이 없다면(visited[1][nx][ny] == false), 큐에 다음 위치 추가

 

'CT' 카테고리의 다른 글

[BOJ] 31단계. 최단 경로  (0) 2024.05.20
[BOJ] 30단계. 그래프와 순회 1  (0) 2024.05.07
[BOJ] 29단계 스택2  (0) 2023.07.21
[BOJ] 28단계 동적 계획법 2  (0) 2023.06.16
[BOJ] 27단계 우선순위 큐  (0) 2023.06.14