ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 백준 1600번 자바 문제 풀이
    취준/알고리즘 2023. 7. 13. 13:06
     

    1600번: 말이 되고픈 원숭이

    첫째 줄에 정수 K가 주어진다. 둘째 줄에 격자판의 가로길이 W, 세로길이 H가 주어진다. 그 다음 H줄에 걸쳐 W개의 숫자가 주어지는데, 0은 아무것도 없는 평지, 1은 장애물을 뜻한다. 장애물이 있

    www.acmicpc.net

    문제 해석

    1,1에 정점에서 h, w의 정점까지 이동하는데 k번만 체스판의 말과 같이 이동할 수 있고 그 외에는 인접한 정점으로만(상, 하, 좌, 우) 움직일 수 있을 때 h, w까지 이동하는 최소 경로수를 구하는 문제이다.

     

    내가 푼것

    처음에 문제를 보고 k번 말처럼 움직이고 그 외에는 인접한 좌표로 움직이면서 BFS 탐색을 하면 되겠구나! 싶었다.

        public static int[] horseMoveX = {-2,2,-1,1};
        public static int[][] horseMoveY = {{-1,1},{-1,1},{-2,2},{-2,2}};
        public static int[] normalMoveX = {1,-1,0,0};
        public static int[] normalMoveY = {0,0,1,-1};

    그래서 2가지의 이동 경로를 다루기 위해 말 처럼 움직이기 위한 X, Y 좌표를 정의하여 주었고

    인접한 정점으로만 움직일 수 있는 X,Y 좌표를 정의하여 주었다.

     

    if(horseMoveCount < k)
        {
            for(int i = 0;i<4;++i)
            {
                for(int t = 0;t<2;++t)
                {
                    int tempX = node.x + horseMoveX[i];
                    int tempY = node.y + horseMoveY[i][t];
    
                    if(tempX <= 0 || tempY <= 0 || tempX > h || tempY > w)
                    {
                        continue;
                    }else if(visited[tempX][tempY] || map[tempX][tempY]==1)
                    {
                        continue;
                    }else{
                        queue.offer(new Node(tempX,tempY));
                        visited[tempX][tempY] = true;
                        answerMap[tempX][tempY] = answerMap[node.x][node.y]+1;
                        ++horseMoveCount;
                    }
                }
            }
        }

    말의 움직임을 체크하는 horseMoveCount변수가 k보다 작으면 말처럼 이동할 수 있는 횟수를 아직 사용을 덜 한 것이기 때문에 

    위에서 정의했던 말처럼 움직이는 X, Y좌표의 값들을 받아서 말 처럼 움직였을 때의 X,Y 좌표가 장애물이 있지 않은 길이고 아직 탐색하지 않은 정점이라면 현재 좌표에서 말 처럼 움직이는 것이 가능하다는 것이기 때문에 queue에 X,Y 좌표를 추가하는 방식으로 구현하였다.

     

    반례

    0 0 0
    0 0 0
    0 1 1
    0 1 0

    내가 설계했던 코드의 반례이다. 내가 설계했던 코드를 이용하게 되면 위와 같은 방식의 길은 탐색할 수 없다고 계산된다.

    나의 코드에 따르면 처음에 1,1에서 시작할 때 horseMoveCount가 k가 0이 아닌 이상 당연히 작을 것이다.

    만약 K = 1이라면 1,1에서 2,3으로 말처럼 이동하고 말 이동 횟수를 전부 사용했기 때문에 4,3에는 접근할 수가 없다.

     

    하지만 실제로는 1,1에서 아래쪽으로 2번 이동하고 3,1에서 오른쪽 대각선으로 말처럼 이동하면 4,3에 접근을 할 수 가 있다.

    이 반례를 통해 배울 수 있는 것은 이미 탐색한 정점이라도 최적의 경로가 아닐 수 도 있고 각 정점마다 말 처럼 이동할 수 있는 경로가 존재한다는 뜻 즉 한번 탐색한 정점이라고 해서 다시 탐색하지 않아도 되는 것이 아니라는 점이다.

     

    원숭이처럼 움직이다가 말처럼 움직이는 경로가 최적의 경로가 될 수도 있고 말처럼 처음에 몇번 움직이고 원숭이처럼 움직이는 경로가

    최적의 경로가 될 수도 있다는 점이다.

     

    풀이

    static boolean[][][] visited;
    class Node{
        int x;
        int y;
        int count;
        int k;
        public Node(int x, int y, int count, int k)
        {
            this.x = x;
            this.y = y;
            this.count = count;
            this.k = k;
        }
    }
    while(!queue.isEmpty())
    {
        Node node = queue.poll();
        if(node.x == h-1 && node.y == w-1)
        {
            return node.count;
        }
        for(int i =0;i<4;++i)
        {
            int mx = node.x + normalMoveX[i];
            int my = node.y + normalMoveY[i];
            if(mx>=0 && my >= 0 && mx < h && my < w && visited[mx][my][node.k] == false && map[mx][my] == 0)
            {
                visited[mx][my][node.k] = true;
                queue.offer(new Node(mx,my,node.count+1,node.k));
            }
        }
        if(node.k < k)
        {
            for(int i=0; i<8;++i)
            {
                int hx = node.x + horseMoveX[i];
                int hy = node.y + horseMoveY[i];
                if(hx>=0 && hy >= 0 &&  hx < h && hy < w && visited[hx][hy][node.k+1] == false && map[hx][hy] == 0)
                {
                    visited[hx][hy][node.k+1] = true;
                    queue.offer(new Node(hx,hy,node.count+1,node.k+1));
                }
            }
        }

    각각의 정점마다 말 처럼 움직일 경우의 수가 있기 때문에 3차원 배열로 선언해 준다.

    예를 들면 visited [1][2][1] = true라고 하면 1,2 좌표로 말처럼 한번 이동해서 왔다는 것을 의미한다. 

     

    그리고 class에 새로운 값인 k와 count를 추가해 준다. 앞서 말했던 것처럼 1,2 좌표로 말처럼 한번 이동해서 온 class 객체를 만들어줘서

    Queue에 추가하면 큐에 이미 있는 정점들을 탐색을 하다가 1,2 좌표를 가지고 말처럼 한번 이동해서 온 정점을 탐색하기 시작할 것이다.

     

    현재 큐에서 꺼내온 객체의 k의 값은 1이다. 말의 이동 제약이 1번으로 주어 졌다면 현재 객체는 더 이상 말처럼 이동할 수 없기 때문에 

    인접한 정점들만 탐색을 할 수 있다. 그래서 if(node.k < k)라는 조건이 주어서 현재 탐색하는 객체가 말 처럼 이동할 수 있는 제약 횟수를

    넘지 않았는지 확인을 한다.

     

    이와 같은 3차원 배열을 이용해 내가 실패했던 반례인 각각의 정점마다 말 처럼 이동할 수 있는 경우의 수, 즉 원숭이처럼 이동하다가 말처럼 럼 이동하는 반례를 처리해 줄 수가 있다.

     

    내 생각에 핵심은 하나의 정점이 있을 때 해당 정점까지 온 경우의 수를 각기 다른 객체로 처리해서 모든 경우의 수를 처리하는 것 같다.

    예를 들어 {2,3} 좌표에 왔을 때 말의 이동 기술을 1번 써서 온 객체, 이동 기술을 2번 써서 온 객체... 등으로 서로 다른 객체로 판별해

    모든 경우의 수를 계산하는 게 핵심인 것 같다.

     

    꽤 어려운 아니 많이 어려운 문제인 거 같다. 코드를 보고 해석하는데도 오래 걸렸고 코드가 어떻게 굴러가는지 머릿속으로 생각하는데도 

    오래 걸린 문제다.. 2차원 배열로 푸는 것도 버거운데 3차원 배열?! 재밌네...

     

    import java.util.*;
    public class Main{
        static int k, w, h;
        static int[][] map;
        static int min = Integer.MAX_VALUE;
        static int[] horseMoveX = {-2, -2, -1, -1, 1, 1, 2, 2};
        static int[] horseMoveY = {-1, 1, -2, 2, -2, 2, -1, 1};
        static int[] normalMoveX = {1,-1,0,0};
        static int[] normalMoveY = {0,0,1,-1};
        static boolean[][][] visited;
        public static void main(String[] args)
        {
            Scanner sc = new Scanner(System.in);
    
            k = sc.nextInt();
            w = sc.nextInt();
            h = sc.nextInt();
    
            map = new int[h][w];
            for(int i = 0; i < h; i++) {
                for(int j = 0; j < w; j++) {
                    map[i][j] = sc.nextInt();
                }
            }
    
            visited = new boolean[h][w][k + 1];
            min = bfs(0, 0);
    
            if(min == Integer.MAX_VALUE)
            {System.out.println(-1);}
            else
            {System.out.println(min);}
    
        }
    
        public static int bfs(int x,int y)
        {
            Queue<Node>queue = new LinkedList<>();
            queue.offer(new Node(x,y,0,0));
            visited[x][y][0] = true;
    
            while(!queue.isEmpty())
            {
                Node node = queue.poll();
                if(node.x == h-1 && node.y == w-1)
                {
                    return node.count;
                }
                for(int i =0;i<4;++i)
                {
                    int mx = node.x + normalMoveX[i];
                    int my = node.y + normalMoveY[i];
                    if(mx>=0 && my >= 0 && mx < h && my < w && visited[mx][my][node.k] == false && map[mx][my] == 0)
                    {
                        visited[mx][my][node.k] = true;
                        queue.offer(new Node(mx,my,node.count+1,node.k));
                    }
                }
                if(node.k < k)
                {
                    for(int i=0; i<8;++i)
                    {
                        int hx = node.x + horseMoveX[i];
                        int hy = node.y + horseMoveY[i];
                        if(hx>=0 && hy >= 0 &&  hx < h && hy < w && visited[hx][hy][node.k+1] == false && map[hx][hy] == 0)
                        {
                            visited[hx][hy][node.k+1] = true;
                            queue.offer(new Node(hx,hy,node.count+1,node.k+1));
                        }
                    }
                }
            }
            return min;
        }
    }
    
    class Node{
        int x;
        int y;
        int count;
        int k;
        public Node(int x, int y, int count, int k)
        {
            this.x = x;
            this.y = y;
            this.count = count;
            this.k = k;
        }
    }

     

    '취준 > 알고리즘' 카테고리의 다른 글

    백준 10971번 자바 문제 풀이  (0) 2023.07.12
    백준 1697번 JAVA 풀이  (0) 2023.07.11
    백준 11724번 자바 풀이 정리  (0) 2023.07.10
    백준 2468번 JAVA 정리  (0) 2023.07.09
    백준 2667  (0) 2023.07.07

    댓글

Designed by Tistory.