developer-ellen
인간 디버거의 로그 찍기
developer-ellen
전체 방문자
오늘
어제
  • 분류 전체보기 (217)
    • 회고록 (0)
    • 취뽀 및 커리어 여정의 Stack (2)
      • SSAFY 7기 (2)
    • 프로그래밍공부 (24)
      • c++ (0)
      • JAVA (3)
      • Spring (5)
      • design pattern (3)
      • BackDB (1)
      • Servlet&JSP (3)
      • Vue (4)
      • JPA (4)
      • Infra (1)
      • Linux (0)
    • AI (3)
      • papers (3)
      • trend (0)
    • 프로젝트진행 (0)
      • 데이터베이스 (0)
      • 서버개발 (0)
      • 인공지능 (0)
      • 하루정리 (0)
    • 포트폴리오 (0)
    • 알고리즘 (158)
      • 알고리즘문풀 (155)
      • 알고리즘공부 (3)
    • 통계공부 (15)
      • 시계열분석 (15)
      • 회귀분석 (0)
    • CS (14)
      • 컴퓨터네트워크 (4)
      • 운영체제 (8)
      • 데이터베이스 (2)
    • 주저리주저리 (0)
      • 필사 (0)
    • 취업관련정보 (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • Arima
  • 삼성코테기출자바풀이
  • 삼성코테파이썬풀이
  • 시계열
  • 카카오코테
  • 데이터분석
  • 삼성코테구현풀이
  • 삼성코테기출
  • BOJ파이썬풀이
  • 삼성코테파이썬
  • SW역량테스트파이썬
  • SW역량테스트파이썬풀이
  • c++디자인패턴
  • 삼성코테자바꿀팁
  • 삼성코테자바풀이
  • 시계열분석
  • 코테파이썬
  • 삼성코테자바준비
  • AR모형
  • c++ 빌더 패턴
  • ARIMA모형
  • 삼성코테준비
  • MA모형
  • 통계학
  • 삼성코테파이썬준비
  • 운영체제인터럽트
  • 통계분석
  • 카카오코테java풀이
  • 백준파이썬풀이
  • 삼성코테구현문제추천

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
developer-ellen

인간 디버거의 로그 찍기

알고리즘/알고리즘문풀

BOJ - 마법사 상어와 복제 23290번 (JAVA)

2022. 3. 1. 23:57

❓ 문제 - 백준 마법사 상어와 복제 23290번 - JAVA 풀이법

 

출처 

(https://www.acmicpc.net/problem/23290)

 

23290번: 마법사 상어와 복제

첫째 줄에 물고기의 수 M, 상어가 마법을 연습한 횟수 S가 주어진다. 둘째 줄부터 M개의 줄에는 물고기의 정보 fx, fy, d가 주어진다. (fx, fy)는 물고기의 위치를 의미하고, d는 방향을 의미한다. 방향

www.acmicpc.net

 

 

📝 문제해결법

1. 이 문제는 DFS(중복조합) + 시뮬레이션으로 문제를 풀었다.

  • 문제의 전체적인 흐름은 복제 -> 물고기 이동 -> 상어의 이동 -> 복제마법 이다.
  • 전체적인 시뮬레이션 구현을 위해 3중 ArrayList 의 map으로 i행 ,j열에 물고기의 정보(방향)등을 저장한다.
  • 처음 물고기 복제를 위해 queue를 통해 현재 map에 존재하는 물고기에 대한 정보를 담아둔다. 
  • 물고기 이동할 때 2중 for 문으로 행,열을 접근하면서 물고기 이동 처리를 할 것이기 때문에 map_copy에 이동한 물고기에 대한 정보를 저정하고 다시 map에 정보를 옮겨준다.
  • 물고기가 이동할 수 없는 곳이 -> 상어가 존재하는 곳이거나 상어의 냄새가 있는 곳인데. 상어의 냄새는 따로 시간초마다 감소하도록 처리하지 않고, 상어가 물고기를 먹은 곳이라면 smell이라는 2중 배열에 먹었을 때 시간 time을 저장하고, 현재 시간에서 smell에 저장된 숫자를 빼서 2 이하라면 갈 수 없음(냄새가 아직 남았음)으로 처리한다.
  • 상어의 이동은 dfs로 중복순열을 통해 순서를 연속된 3방향의 방향을 구하고, 해당 경우일 때 먹을 수 있는 물고기의 수를 카운트해서 최대 먹을 수 있는 물고기 수의 수만큼 먹을 수 있으면 list에 넣어준 후 정렬한다.
  • 정렬하면 사전 순으로 정렬이 되므로 0인덱스의 방향으로 상어를 3방향 이동시키고, 만약 물고기 있는 곳으로 이동한다면 물고기의 정보를 없애고 냄새를 남긴다.
  • 위에서 queue에 저장한 복제할 정보를 복제마법을 통해 map에 물고기 정보를 넣어준다.
  • 계속 반복 후 해당 시간 이후로 남은 물고기 갯수를 출력한다.

2. 물고기 이동

  • 해당 map에 물고기의 방향으로 갈 수 없음(상어 존재 or 냄새 존재 or 범위 넘음)면 45도 방향 이동하고, 모든 방향이 다 불가하면 map_copy의 현재 물고기에 위치에 정보를 그대로 넣어준다.
ArrayList<ArrayList<ArrayList<Integer>>> map_copy = new ArrayList<>();
            for(int i=0;i<4;i++){
                map_copy.add(new ArrayList<>());
                for(int j=0;j<4;j++){
                    map_copy.get(i).add(new ArrayList<>());
                }
            }

            for(int i=0;i<4;i++){
                for(int j=0;j<4;j++){
                    for(int k=0;k<map.get(i).get(j).size();k++){
                        int first_dir =  map.get(i).get(j).get(k);
                        boolean check = true;
                        int nx = i, ny = j;

                        for(int d=0;d<8;d++){
                            int nd = ((first_dir-d)+8) % 8;
                            nx = i + dx[nd];
                            ny = j + dy[nd];
                            if(nx < 0 || nx >= 4 || ny < 0 || ny >= 4) continue;
                            if(smell[nx][ny] != 0 && time - smell[nx][ny] <= 2) continue;
                            if(nx == s_x && ny == s_y) continue;
                            map_copy.get(nx).get(ny).add(nd);
                            check = false;
                            break;
                        }

                        if(check){
                            map_copy.get(i).get(j).add(first_dir);
                        }

                    }

                }
            }

3. DFS(상어의 3연속 방향의 중복 순열 구하기)

  • DFS 방식으로 중복 순열을 구현함
  • 만약 depth가 3, 즉 3방향의 한 경우가 만들어지면 eatCount() 함수를 통해 해당 경우일 때 얼만큼 물고기를 먹을 수 있을 지 카운트
  • 만약 지금까지 최대 먹을 수 있는 물고기의 수보다 더 많은 수의 물고기를 먹을 수 있다면 moving_list를 clear 한 후 자신의 경우의수를 넣어주고 최대 먹을 수 있는 물고기 수 갱신
  • 만약 지금까지 최대 먹을 수 있는 물고기의 수만큼 물고기를 먹을 수 있다면 moving_list에 자신의 경우를 넣어줌
// 상어가 이동할 수 있는 3연속 방향의 중복순열을 구하는 dfs
    public static void dfs(int depth, int[] arr){
        if(depth==3){
            int[] arr_copy = new int[3];
            String str = "";
            for(int i=0;i<3;i++){
                arr_copy[i] = arr[i];
                str += String.valueOf(arr_copy[i]);
            }

            int cnt = eatCount(str);

            if(max_eat < cnt){
                moving_list.clear();
                moving_list.add(Integer.parseInt(str));
                max_eat = cnt;
            } else if (max_eat == cnt){
                moving_list.add(Integer.parseInt(str));
            }

            return;
        }
        for(int i=1;i<=4;i++){
            arr[depth] = i;
            dfs(depth+1, arr);
        }
    }

4. eatCount -> 해당 경우일 때 얼만큼의 물고기를 먹을 수 있는지 카운트하는 함수

  • 해당 방향으로 이동하다가 범위를 넘어가면 이동할 수 없는 경우로 취급하면서 리턴
  • 어떤 방향으로 갔다가 다시 돌아오는 경우는 한번만 물고기를 먹을 수 있으므로 물고기를 먹었다는 visited 배열을 통해 체크해줌
  • 맨 처음에 물고기와 상어가 같이 있는 공간에서 상어의 움직임이 시작될 수 있음(문제 조건) -> 따라서 맨 처음 시작점에서는 물고기를 먹을 수 없지만 어떤 방향으로 갔다가 다시 제자리로 돌아오면 해당 위치의 물고기를 먹을 수 있음
    // 해당 방향으로 3연속 이동할 때 먹을 수 있는 물고기 갯수 카운트
    public static int eatCount(String dir){
        int x = s_x;
        int y = s_y;
        int eat_cnt = 0;
        boolean[][] vistied = new boolean[4][4];

        for(int i=0;i<3;i++){
            int d = dir.charAt(i) - '1';
            x += s_dx[d];
            y += s_dy[d];
            if(x < 0 || y < 0 || x >= 4 || y >= 4){
                return -1;
            }
            if(!vistied[x][y]) eat_cnt += map.get(x).get(y).size();
            vistied[x][y] = true;
        }
        return eat_cnt;
    }

 

5. 느낀점

  • 빡구현... 우선 문제를 3번 이상 보면서 문제의 조건을 잘 살피는 게 제일 중요한 것 같다..
  • 4번째 깨달은 상어가 이동했다가 다시 제자리로 왔을 때 물고기는 한번만 먹을 수 있음과 처음에 상어와 물고기가 같은 자리에서 상어가 이동할 때는 처음 지점에서 시작부터 먹을 수 없지만 다시 자기 자리로 이동하면 그 땐 먹을 수 있음... -> 이거 엄청 중요한 것 같다... 이것 때문에 65퍼에서 계속 틀렸습니다 뜨고.. 이틀 이상 고민했다...ㅠㅠ 
  • 구현은 문제의 조건을 하나하나 다 살피는 것과 내가 구현한 것이 해당 조건에 틀리지 않은지 항상 살피는 능력이 중요한 것 같다...!!!!!!!

 

💻 소스코드

// BOJ - 마법사 상어와 복제(23290번)
// DFS(중복조합) + 시뮬레이션

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

public class Main_23290 {
    public static int m, s;
    public static int s_x, s_y;
    public static int[][] smell;
    public static int[] dx = {0, -1, -1, -1, 0, 1, 1, 1};
    public static int[] dy = {-1, -1, 0, 1, 1, 1, 0, -1};
    public static ArrayList<ArrayList<ArrayList<Integer>>> map;
    public static ArrayList<Integer> moving_list;
    public static int max_eat;
    public static int[] s_dx = {-1, 0, 1, 0};
    public static int[] s_dy = {0, -1, 0, 1};
    public static class Fish{
        int x;
        int y;
        int d;
        public Fish(int x, int y, int d){
            this.x = x;
            this.y = y;
            this.d = d;
        }
    }
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine(), " ");
        m = Integer.parseInt(st.nextToken());
        s = Integer.parseInt(st.nextToken());
        smell = new int[4][4];
        Queue<Fish> q_first = new LinkedList<>();

        map = new ArrayList<>();
        for(int i=0;i<4;i++){
            map.add(new ArrayList<>());
            for(int j=0;j<4;j++){
                map.get(i).add(new ArrayList<>());
            }
        }

        for(int i=0;i<m;i++){
            st = new StringTokenizer(br.readLine(), " ");
            int x = Integer.parseInt(st.nextToken());
            int y = Integer.parseInt(st.nextToken());
            int d = Integer.parseInt(st.nextToken());
            map.get(x-1).get(y-1).add(d-1);
        }

        st = new StringTokenizer(br.readLine(), " ");
        s_x = Integer.parseInt(st.nextToken()) -1;
        s_y = Integer.parseInt(st.nextToken()) -1;

        int time = 1;
        while (time<=s){
            // 복제
            q_first.clear();
            for(int i=0;i<4;i++){
                for(int j=0;j<4;j++){
                    for(int k=0;k<map.get(i).get(j).size();k++){
                        q_first.offer(new Fish(i, j, map.get(i).get(j).get(k)));
                    }
                }
            }

            // 물고기 이동
            ArrayList<ArrayList<ArrayList<Integer>>> map_copy = new ArrayList<>();
            for(int i=0;i<4;i++){
                map_copy.add(new ArrayList<>());
                for(int j=0;j<4;j++){
                    map_copy.get(i).add(new ArrayList<>());
                }
            }

            for(int i=0;i<4;i++){
                for(int j=0;j<4;j++){
                    for(int k=0;k<map.get(i).get(j).size();k++){
                        int first_dir =  map.get(i).get(j).get(k);
                        boolean check = true;
                        int nx = i, ny = j;

                        for(int d=0;d<8;d++){
                            int nd = ((first_dir-d)+8) % 8;
                            nx = i + dx[nd];
                            ny = j + dy[nd];
                            if(nx < 0 || nx >= 4 || ny < 0 || ny >= 4) continue;
                            if(smell[nx][ny] != 0 && time - smell[nx][ny] <= 2) continue;
                            if(nx == s_x && ny == s_y) continue;
                            map_copy.get(nx).get(ny).add(nd);
                            check = false;
                            break;
                        }

                        if(check){
                            map_copy.get(i).get(j).add(first_dir);
                        }

                    }

                }
            }

            map = map_copy;

            // 상어의 이동 -> 중복 순열...dfs
            max_eat = 0;
            moving_list = new ArrayList<>();
            dfs(0, new int[3]);
            Collections.sort(moving_list);
            String moving_str = String.valueOf(moving_list.get(0));
                for (int i = 0; i < 3; i++) {
                    int dir = moving_str.charAt(i) - '1';
                    s_x += s_dx[dir];
                    s_y += s_dy[dir];
                    // 죽이면서 냄새 뿌리기
                    if (map.get(s_x).get(s_y).size() > 0) {
                        map.get(s_x).get(s_y).clear();
                        smell[s_x][s_y] = time;
                    }

                }

            // 복제 마법

            while (!q_first.isEmpty()){
                Fish f = q_first.poll();
                map.get(f.x).get(f.y).add(f.d);
            }


            time++;
        }


        // 물고기 갯수 출력
        int ans = 0;
        for(int i=0;i<4;i++){
            for(int j=0;j<4;j++){
                ans += map.get(i).get(j).size();
            }
        }
        System.out.println(ans);
    }

    // 상어가 이동할 수 있는 3연속 방향의 중복순열을 구하는 dfs
    public static void dfs(int depth, int[] arr){
        if(depth==3){
            int[] arr_copy = new int[3];
            String str = "";
            for(int i=0;i<3;i++){
                arr_copy[i] = arr[i];
                str += String.valueOf(arr_copy[i]);
            }

            int cnt = eatCount(str);

            if(max_eat < cnt){
                moving_list.clear();
                moving_list.add(Integer.parseInt(str));
                max_eat = cnt;
            } else if (max_eat == cnt){
                moving_list.add(Integer.parseInt(str));
            }

            return;
        }
        for(int i=1;i<=4;i++){
            arr[depth] = i;
            dfs(depth+1, arr);
        }
    }

    // 해당 방향으로 3연속 이동할 때 먹을 수 있는 물고기 갯수 카운트
    public static int eatCount(String dir){
        int x = s_x;
        int y = s_y;
        int eat_cnt = 0;
        boolean[][] vistied = new boolean[4][4];

        for(int i=0;i<3;i++){
            int d = dir.charAt(i) - '1';
            x += s_dx[d];
            y += s_dy[d];
            if(x < 0 || y < 0 || x >= 4 || y >= 4){
                return -1;
            }
            if(!vistied[x][y]) eat_cnt += map.get(x).get(y).size();
            vistied[x][y] = true;
        }
        return eat_cnt;
    }
}

'알고리즘 > 알고리즘문풀' 카테고리의 다른 글

BOJ - 서강그라운드 14938번 (JAVA)  (0) 2022.03.07
BOJ - 주사위 굴리기2 23288번 (JAVA)  (0) 2022.03.04
BOJ - 입국심사 3079번 (JAVA)  (0) 2022.02.21
BOJ - 빙산 2573번 (JAVA)  (0) 2022.02.19
SW Expert Academy - 무선 충전 (JAVA)  (0) 2022.02.17
    '알고리즘/알고리즘문풀' 카테고리의 다른 글
    • BOJ - 서강그라운드 14938번 (JAVA)
    • BOJ - 주사위 굴리기2 23288번 (JAVA)
    • BOJ - 입국심사 3079번 (JAVA)
    • BOJ - 빙산 2573번 (JAVA)
    developer-ellen
    developer-ellen

    티스토리툴바