반응형

* 핵심 개념

시험 당시에는 17143번 낚시왕 문제보다 복잡해보여서 풀지 않았는데,

다시 풀어보니 역시 풀만한 문제였다. 

특별한 알고리즘 기법이 필요없이 단순히 새로운 크기의 맵을 하나 더 만들어서

새로운 맵에 시뮬레이션한 결과를 저장하고 이를 다시 원래맵에 돌려주기만 하면 된다.

* 문제

문제

미세먼지를 제거하기 위해 구사과는 공기청정기를 설치하려고 한다. 공기청정기의 성능을 테스트하기 위해 구사과는 집을 크기가 R×C인 격자판으로 나타냈고, 1×1 크기의 칸으로 나눴다. 구사과는 뛰어난 코딩 실력을 이용해 각 칸 (r, c)에 있는 미세먼지의 양을 실시간으로 모니터링하는 시스템을 개발했다. (r, c)는 r행 c열을 의미한다.

공기청정기는 항상 왼쪽 열에 설치되어 있고, 크기는 두 행을 차지한다. 공기청정기가 설치되어 있지 않은 칸에는 미세먼지가 있고, (r, c)에 있는 미세먼지의 양은 Ar,c이다.

1초 동안 아래 적힌 일이 순서대로 일어난다.

  1. 미세먼지가 확산된다. 확산은 미세먼지가 있는 모든 칸에서 동시에 일어난다.
    • (r, c)에 있는 미세먼지는 인접한 네 방향으로 확산된다.
    • 인접한 방향에 공기청정기가 있거나, 칸이 없으면 그 방향으로는 확산이 일어나지 않는다.
    • 확산되는 양은 Ar,c/5이고 소수점은 버린다.
    • (r, c)에 남은 미세먼지의 양은 Ar,c - (Ar,c/5)×(확산된 방향의 개수) 이다.
  2. 공기청정기가 작동한다.
    • 공기청정기에서는 바람이 나온다.
    • 위쪽 공기청정기의 바람은 반시계방향으로 순환하고, 아래쪽 공기청정기의 바람은 시계방향으로 순환한다.
    • 바람이 불면 미세먼지가 바람의 방향대로 모두 한 칸씩 이동한다.
    • 공기청정기에서 부는 바람은 미세먼지가 없는 바람이고, 공기청정기로 들어간 미세먼지는 모두 정화된다.

공기청정기의 바람은 다음과 같은 방향으로 순환한다.

방의 정보가 주어졌을 때, T초가 지난 후 구사과의 방에 남아있는 미세먼지의 양을 구해보자.

입력

첫째 줄에 R, C, T (6 ≤ R, C ≤ 50, 1 ≤ T ≤ 1,000) 가 주어진다.

둘째 줄부터 R개의 줄에 Ar,c (-1 ≤ Ar,c ≤ 1,000)가 주어진다. 공기청정기가 설치된 곳은 Ar,c가 -1이고, 나머지 값은 미세먼지의 양이다. -1은 2번 위아래로 붙어져 있고, 가장 윗 행, 아랫 행과 두 칸이상 떨어져 있다.

출력

첫째 줄에 T초가 지난 후 구사과 방에 남아있는 미세먼지의 양을 출력한다.

* 소스 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import java.io.*;
import java.util.*;
 
public class Main {
    public static StringTokenizer stk;
    public static Cleaner[] cleaner_pos = new Cleaner[2];   //공기청정기 위치 저장
    public static int r, c, t;
    public static int[][] map;
    public static int[] dx = {1-100};
    public static int[] dy = {001-1};
 
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        stk = new StringTokenizer(br.readLine());
        r = Integer.parseInt(stk.nextToken());
        c = Integer.parseInt(stk.nextToken());
        t = Integer.parseInt(stk.nextToken());
        map = new int[r][c];
        int idx = 0;
        for (int i = 0; i < r; i++) {
            stk = new StringTokenizer(br.readLine());
            for (int j = 0; j < c; j++) {
                map[i][j] = Integer.parseInt(stk.nextToken());
                if (map[i][j] == -1) {
                    cleaner_pos[idx++= new Cleaner(i, j);
                }
            }
        }
        for (int i = 0; i < t; i++) {
            spreadStart(new int[r][c]);
            cleanerStart(new int[r][c]);
        }
        getAns();
    }
 
    public static void spreadStart(int[][] nmap) {
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                nmap[i][j] += map[i][j];
                if (map[i][j] < 5continue;
                int spreadCnt = map[i][j] / 5;
                for (int k = 0; k < 4; k++) {
                    int ny = i + dy[k];
                    int nx = j + dx[k];
                    //해당 맵에 퍼트릴 수 있는지 확인
                    if (ny >= 0 && ny < r && nx >= 0 && nx < c && map[ny][nx] != -1) {
                        nmap[i][j] -= spreadCnt;
                        nmap[ny][nx] += spreadCnt;
                    }
                }
            }
        }
        spreadCopy(nmap);
    }
 
    public static void cleanerStart(int[][] nmap) {
        for (int idx = 0; idx < 2; idx++) {
            Cleaner curr = cleaner_pos[idx];
            int ny = curr.r;
            int nx = curr.c + 1;
            //오른쪽으로 끝까지
            while (nx < c - 1) {
                nmap[ny][nx + 1= map[ny][nx];
                nx++;
            }
            //상하로 끝까지
            if (idx == 0) {
                while (ny > 0) {
                    nmap[ny - 1][nx] = map[ny][nx];
                    ny--;
                }
            } else {
                while (ny < r - 1) {
                    nmap[ny + 1][nx] = map[ny][nx];
                    ny++;
                }
            }
            //좌측으로 끝까지
            while (nx > 0) {
                nmap[ny][nx - 1= map[ny][nx];
                nx--;
            }
            //공기청정기 위치 전까지
            if (idx == 0) {
                while (ny < curr.r - 1) {
                    nmap[ny + 1][nx] = map[ny][nx];
                    ny++;
                }
            } else {
                while (ny > curr.r + 1) {
                    nmap[ny - 1][nx] = map[ny][nx];
                    ny--;
                }
            }
        }
        cleanerCopy(nmap);
    }
 
    public static void spreadCopy(int[][] nmap) {
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                map[i][j] = nmap[i][j];
            }
        }
    }
 
    public static void cleanerCopy(int[][] nmap) {
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                if (i == 0 || i == r - 1 || j == 0 || j == c - 1 || i == cleaner_pos[0].r || i == cleaner_pos[1].r) {
                    map[i][j] = nmap[i][j];
                }
            }
        }
        map[cleaner_pos[0].r][cleaner_pos[0].c] = -1;
        map[cleaner_pos[1].r][cleaner_pos[1].c] = -1;
    }
 
    public static void getAns() {
        int ans = 0;
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                if (map[i][j] <= 0continue;
                ans += map[i][j];
            }
        }
        System.out.println(ans);
    }
 
    public static class Cleaner {
        int r, c;
 
        public Cleaner(int r, int c) {
            this.r = r;
            this.c = c;
        }
    }
}
cs


반응형
반응형

* Review

2019 상반기 공채 오전반 복원문제중 2번문제였다.

기본 삼성유형으로 알려진 BFS, DFS에서 그냥 구현문제가 나올줄은 몰랏지만

쉬웠다는 평이 많은 상반기 코테에서 1솔로 가능할지 의문이다.

* 핵심 개념

이 문제에서는 주의할 점이 두가지 있다.

1. 물고기들 각각을 움직일 때 바로 움직이지 않고, 한번에 움직여야 한다.

필자는 Fish 배열을 만들어 정보를 저장하고 상어 움직임이 끝낫을 때 한번에 이동했다.

물론, 조건에 따라 해당 좌표에 자신보다 작은 상어가 있다면 Fish 배열에 null로 바꿔주면 된다.

2. 시간 속도를 줄이려면 움직여야 하는 거리 % (Width or Depth * 2 - 2) 계산을 하고 움직이면 된다.(584ms -> 308ms)

아쉽게도 시험장에서 30분을 고민하다 이 공식을 못찾았다.

이 두가지 조건만 만족하면 어렵지 않게 풀수있는 기출문제다.

* 문제

문제

낚시왕이 상어 낚시를 하는 곳은 크기가 R×C인 격자판으로 나타낼 수 있다. 격자판의 각 칸은 (r, c)로 나타낼 수 있다. r은 행, c는 열이고, (R, C)는 아래 그림에서 가장 오른쪽 아래에 있는 칸이다. 칸에는 상어가 최대 한 마리 들어있을 수 있다. 상어는 크기와 속도를 가지고 있다.

낚시왕은 가장 처음에 1번 열의 한 칸 왼쪽에 있다. 다음은 1초 동안 일어나는 일이며, 아래 적힌 순서대로 일어난다. 낚시왕은 가장 오른쪽 열의 오른쪽 칸에 이동하면 이동을 멈춘다.

  1. 낚시왕이 오른쪽으로 한 칸 이동한다.
  2. 낚시왕이 있는 열에 있는 상어 중에서 가장 땅과 가까운 상어를 잡는다. 상어를 잡으면 격자판에서 잡은 상어가 사라진다.
  3. 상어가 이동한다.

상어는 입력으로 주어진 속도로 이동하고, 속도의 단위는 칸/초이다. 상어가 이동하려고 하는 칸이 격자판의 경계인 경우에는 방향을 반대로 바꿔서 속력을 유지한채로 이동한다.

왼쪽 그림의 상태에서 1초가 지나면 오른쪽 상태가 된다. 상어가 보고 있는 방향이 속도의 방향, 왼쪽 아래에 적힌 정수는 속력이다. 오른쪽 위에 상어를 구분하기 위해 문자를 적어 놓았다.

상어가 이동을 마친 후에 한 칸에 상어가 두 마리 이상 있을 수 있다. 이때는 크기가 가장 큰 상어가 나머지 상어를 모두 잡아먹는다.

낚시왕이 상어 낚시를 하는 격자판의 상태가 주어졌을 때, 낚시왕이 잡은 상어 크기의 합을 구해보자.

입력

첫째 줄에 격자판의 크기 R, C와 상어의 수 M이 주어진다. (2 ≤ R, C ≤ 100, 0 ≤ M ≤ R×C)

둘째 줄부터 M개의 줄에 상어의 정보가 주어진다. 상어의 정보는 다섯 정수 r, c, s, d, z (1 ≤ r ≤ R, 1 ≤ c ≤ C, 0 ≤ s ≤ 1000, 1 ≤ d ≤ 4, 1 ≤ z ≤ 10000) 로 이루어져 있다. (r, c)는 상어의 위치, s는 속력, d는 이동 방향, z는 크기이다. d가 1인 경우는 위, 2인 경우는 아래, 3인 경우는 오른쪽, 4인 경우는 왼쪽을 의미한다.

두 상어가 같은 크기를 갖는 경우는 없고, 하나의 칸에 둘 이상의 상어가 있는 경우는 없다.

출력

낚시왕이 잡은 상어 크기의 합을 출력한다.

* 소스 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import java.io.*;
import java.util.*;
 
public class Main {
    public static StringTokenizer stk;
    public static StringBuilder sb = new StringBuilder();
    public static int[] dy = {-1100};
    public static int[] dx = {001-1};
    public static int width, depth, n, ans = 0;
    public static Fish[] fishInfo = new Fish[10001];
    public static int[][] map;
 
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        stk = new StringTokenizer(br.readLine());
        depth = Integer.parseInt(stk.nextToken());
        width = Integer.parseInt(stk.nextToken());
        n = Integer.parseInt(stk.nextToken());
        map = new int[depth + 1][width + 1];
        for (int i = 1; i <= n; i++) {
            stk = new StringTokenizer(br.readLine());
            int r = Integer.parseInt(stk.nextToken());
            int c = Integer.parseInt(stk.nextToken());
            int s = Integer.parseInt(stk.nextToken());
            int d = Integer.parseInt(stk.nextToken());
            int z = Integer.parseInt(stk.nextToken());
            fishInfo[z] = new Fish(z, c, r, d - 1, s);
            map[r][c] = z;
        }
        for (int i = 1; i <= width; i++) {
            //작살로 물고기 잡음
            for (int j = 1; j <= depth; j++) {
                if (map[j][i] != 0) {
                    fishInfo[map[j][i]] = null;
                    ans += map[j][i];
                    map[j][i] = 0;
                    break;
                }
            }
            //물고기 이동
            for (int j = 1; j <= 10000; j++) {
                if (fishInfo[j] == nullcontinue;
                Fish curr = fishInfo[j];
                int nx = curr.x;
                int ny = curr.y;
                int move = curr.speed;
                int d = curr.d;
                map[ny][nx] = 0;
                switch (d) {    //방향따라 case 다르게
                    //상하
                    case 0:
                    case 1:
                        move %= (depth * 2 - 2);
                        while (move > 0) {
                            if (ny == 1) {
                                d = 1;
                            }
                            if (ny == depth) {
                                d = 0;
                            }
                            ny += dy[d];
                            move--;
                        }
                        fishInfo[curr.idx] = new Fish(curr.idx, nx, ny, d, curr.speed);
                        break;
                    //좌우
                    case 2:
                    case 3:
                        move %= (width * 2 - 2);
                        while (move > 0) {
                            if (nx == 1) d = 2;
                            if (nx == width) d = 3;
                            nx += dx[d];
                            move--;
                        }
                        fishInfo[curr.idx] = new Fish(curr.idx, nx, ny, d, curr.speed);
                        break;
                }
            }
            for (int j = 1; j <= 10000; j++) {
                if (fishInfo[j] == nullcontinue;
                Fish curr = fishInfo[j];
                if (map[curr.y][curr.x] < curr.idx) {   //맵에 저장된 물고기보다 본인이 더 크면
                    fishInfo[map[curr.y][curr.x]] = null;
                    map[curr.y][curr.x] = curr.idx;
                }
            }
        }
        System.out.println(ans);
    }
 
    public static class Fish {
        int idx, x, y, d, speed;
 
        public Fish(int idx, int x, int y, int d, int speed) {
            this.idx = idx;
            this.x = x;
            this.y = y;
            this.d = d;
            this.speed = speed;
        }
    }
}
cs


반응형
반응형

* 핵심 개념

삼성 4월 역량테스트에 나왔던 문제다.

Backtracking으로 구현하면 되지만, 최적화를 하지 않으면 시간이 무지오래걸린다.

우선, 구현 순서에 대해 알아보면

  1. 10*10 맵을 돌면서 1인지 확인한다.
  2. 1이 있다면 5*5 색종이를 덮을 수 있는지 확인한다.
  3. 색종이를 덮을 수 있다면 해당 맵을 0으로 바꾼다.
  4. 색종이를 덮을 수 없다면 더 작은 색종이를 확인한다.

이런 방식으로 진행되는 Backtracking 방식이다.

하지만, 단순하게 조건만 따라 구현하면 시간 초과가 날 수밖에 없다.

이를 위해 다음과 같은 방법을 사용한다.

  1. 매개변수로 row 값을 넘겨준다. 맵을 탐색할 때 row부터 시작하면 이전 줄은 확인하지 않아도 되므로 연산을 줄일 수 있다.    >> int r 파라미터로 사용
  2. 입력 시에 1의 총 개수를 구하고 파라미터에 색종이로 지운 1의 개수를 계산하면서 넘긴다.        >> int total 파라미터로 사용
    • 만약 지운 1의 개수와 입력받은 1의 개수가 같다면 ans에 최소값을 저장하고 return한다.
  3. 큰 색종이부터 들어갈 수 있는지 확인해 보기 때문에, 큰 색종이로 덮을 수 있다면 작은 색종이는 확인해볼 필요 없이 가능하다. >> boolean flag 이용
    • 1*1 색종이까지 확인했는데도 색종이를 넣을 수 없다면 함수를 return한다.
  4. 해당 좌표를 바꾼 후에 아직도 1이 남아있으면 유효하지 않은 경우이므로 return한다.        >> 가지치기

가지치기 때문에 정말 고생이 많았던 문제다.

코드가 깔끔하지는 않지만 누군가에게 도움이 되길 바라며 공유한다.

* 문제

문제

<그림 1>과 같이 정사각형 모양을 한 다섯 종류의 색종이가 있다. 색종이의 크기는 1×1, 2×2, 3×3, 4×4, 5×5로 총 다섯 종류가 있으며, 각 종류의 색종이는 5개씩 가지고 있다.

<그림 1>

색종이를 크기가 10×10인 종이 위에 붙이려고 한다. 종이는 1×1 크기의 칸으로 나누어져 있으며, 각각의 칸에는 0 또는 1이 적혀 있다. 1이 적힌 칸은 모두 색종이로 덮여져야 한다. 색종이를 붙일 때는 종이의 경계 밖으로 나가서는 안되고, 겹쳐도 안된다. 또, 칸의 경계와 일치하게 붙여야 한다. 0이 적힌 칸에는 색종이가 있으면 안된다.

종이가 주어졌을 때, 1이 적힌 모든 칸을 붙이는데 필요한 색종이의 최소 개수를 구해보자.

입력

총 10개의 줄에 종이의 각 칸에 적힌 수가 주어진다.

출력

모든 1을 덮는데 필요한 색종이의 최소 개수를 출력한다. 1을 모두 덮는 것이 불가능한 경우에는 -1을 출력한다.

* 소스 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import java.io.*;
import java.util.*;
 
public class Main {
    public static StringTokenizer stk;
    public static StringBuilder sb = new StringBuilder();
    public static int[] paper = {055555};
    public static int[][] map = new int[10][10];
    public static int ans = Integer.MAX_VALUE, one_cnt = 0;
 
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        for (int i = 0; i < 10; i++) {
            stk = new StringTokenizer(br.readLine());
            for (int j = 0; j < 10; j++) {
                map[i][j] = Integer.parseInt(stk.nextToken());
                one_cnt += map[i][j];       //1의 개수 세기
            }
        }
        //r = 해당 row, cnt = 사용한 색종이수, total = 제거한 1의 개수
        Backtracking(000);
        System.out.println(ans == Integer.MAX_VALUE ? -1 : ans);
    }
 
    public static void Backtracking(int r, int cnt, int total) {
        if (ans <= cnt) return;      //현재 값보다 색종이를 많이쓰면 가지치기
        if (total == one_cnt) {      //1을 다 채운 경우
            ans = Math.min(ans, cnt);
            return;
        }
        for (int i = r; i < 10; i++) {     //r부터 시작
            for (int j = 0; j < 10; j++) {
                if (map[i][j] == 1) {
                    boolean flag = false;  //큰 색종이로 덮을 수 있으면 이후 색종이는 확인해 보지 않아도 된다
                    for (int k = 5; k >= 1; k--) {
                        if ((i + k) <= 10 && (j + k) <= 10 && paper[k] > 0) {
                            if (!flag) {
                                flag = check(i, j, k); //k*k 색종이를 덮을 수 있으면 check = true
                            }
                            if (flag) {
                                setVisited(i, j, k);
                                paper[k]--;
                                Backtracking(i, cnt + 1, total + k * k);
                                paper[k]++;
                                setVisited(i, j, k);
                            }
                        }
                    }
                    if (!flag) return;          //색종이를 못덮는 경우
                }
                if (map[i][j] == 1return;     //덮고나서도 해당좌표를 못지우는경우
            }
        }
    }
 
    //size만큼의 색종이를 사용할 수 있는지 확인
    public static boolean check(int r, int c, int size) {
        for (int i = r; i < r + size; i++) {
            for (int j = c; j < c + size; j++) {
                if (map[i][j] == 0return false;
            }
        }
        return true;
    }
 
    //방문한 점을 XOR 연산
    public static void setVisited(int r, int c, int size) {
        for (int i = r; i < r + size; i++) {
            for (int j = c; j < c + size; j++) {
                map[i][j] = map[i][j] ^ 1;
            }
        }
    }
}
cs


반응형
반응형

* 핵심 개념

4월 삼성역테 기출문제이다.

1. 3명의 궁수의 위치를 지정하기 위해 m개 가로열 중 3자리 선택(DFS로 구현)

2. 각 궁수의 위치가 정해지면 3명의 궁수를 BFS 탐색

주의1) 같은 길이면 왼쪽에 있는 몬스터를 먼저 잡기 때문에 dx, dy를 왼쪽, 가운데, 오른쪽 순서대로 해야한다.

주의2) 궁수가 같은 몬스터를 공격할 수도 있기 때문에 찾은 몬스터를 바로 0으로 바꾸지 말것

* 문제

문제

캐슬 디펜스는 성을 향해 몰려오는 적을 잡는 턴 방식의 게임이다. 게임이 진행되는 곳은 크기가 N×M인 격자판으로 나타낼 수 있다. 격자판은 1×1 크기의 칸으로 나누어져 있고, 각 칸에 포함된 적의 수는 최대 하나이다. 격자판의 N번행의 바로 아래(N+1번 행)의 모든 칸에는 성이 있다.

성을 적에게서 지키기 위해 궁수 3명을 배치하려고 한다. 궁수는 성 위의 칸에 배치할 수 있고, 하나의 칸에는 최대 1명의 궁수만 있을 수 있다. 각각의 턴마다 궁수는 적 하나를 공격할 수 있다. 궁수가 공격하는 적은 거리가 D이하인 적 중에서 가장 가까운 적이고, 그러한 적이 여럿일 경우에는 가장 왼쪽에 있는 적을 공격한다. 같은 적이 여러 궁수에게 공격당할 수 있다. 공격받은 적은 게임에서 제외된다. 궁수의 공격이 끝나면, 적이 이동한다. 적은 아래로 한 칸 이동하며, 성이 있는 칸으로 이동한 경우에는 게임에서 제외된다. 모든 적이 격자판에서 제외되면 게임이 끝난다. 

게임 설명에서 보다시피 궁수를 배치한 이후의 게임 진행은 정해져있다. 따라서, 이 게임은 궁수의 위치가 중요하다. 격자판의 상태가 주어졌을 때, 궁수의 공격으로 제거할 수 있는 적의 최대 수를 계산해보자.

격자판의 두 위치 (r1, c2), (r2, c2)의 거리는 |r1-r2| + |c1-c2|이다.

입력

첫째 줄에 격자판 행의 수 N, 열의 수 M, 궁수의 공격 거리 제한 D가 주어진다. 둘째 줄부터 N개의 줄에는 격자판의 상태가 주어진다. 0은 빈 칸, 1은 적이 있는 칸이다.

출력

첫째 줄에 궁수의 공격으로 제거할 수 있는 적의 최대 수를 출력한다.

* 소스 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import java.io.*;
import java.util.*;
 
public class Main {
    public static StringTokenizer stk;
    public static StringBuilder sb = new StringBuilder();
    public static int[] dx = {0-10};
    public static int[] dy = {-101};
    public static int n, m, d, ans = 0;
    public static boolean[] used;   //nCr 구할 때 사용하는 배열
    public static int[][] map;
 
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        stk = new StringTokenizer(br.readLine());
        n = Integer.parseInt(stk.nextToken());
        m = Integer.parseInt(stk.nextToken());
        d = Integer.parseInt(stk.nextToken());
        map = new int[n + 1][m];
        used = new boolean[m];
        for (int i = 0; i < n; i++) {
            stk = new StringTokenizer(br.readLine());
            for (int j = 0; j < m; j++) {
                map[i][j] = Integer.parseInt(stk.nextToken());
            }
        }
        nCr(00);
        System.out.println(ans);
    }
 
    //m개의 자리중 3개의 궁수 자리 선택
    public static void nCr(int idx, int cnt) {
        if (cnt == 3) {
            int[][] nmap = new int[n + 1][m];
            for (int i = 0; i < n + 1; i++) {
                for (int j = 0; j < m; j++) {
                    nmap[i][j] = map[i][j];
                }
            }
            bfs(nmap);
            return;
        }
        for (int i = idx; i < m; i++) {
            if (!used[i]) {
                used[i] = true;
                nCr(i, cnt + 1);
                used[i] = false;
            }
        }
    }
 
    public static void bfs(int[][] nmap) {
        int cnt = 0;
        for (int i = n; i > 0; i--) {   //궁수의 포지션을 한칸씩 앞으로 이동
            int archerIdx = 0;
            Queue<Pair> q = new LinkedList<>();
            for (int j = 0; j < m; j++) {
                //nmap에서 궁수 위치는 2
                if (used[j]) {
                    nmap[i][j] = 2;
                    q.add(new Pair(i - 1, j, 1, archerIdx++));
                } else {    //궁수 위치가 아니면 0
                    nmap[i][j] = 0;
                }
            }
 
            boolean[] isEnemyFind = new boolean[3];     //각 궁수가 몬스터를 찾았는지
            boolean[][][] visited = new boolean[n][m][3];   //각 궁수가 해당 좌표를 방문했는지
            boolean[][] isAlreadyFindLocation = new boolean[n][m];  //이미 다른 궁수가 찾은 목표물인지
            ArrayList<Pair> alist = new ArrayList<>();
 
            while (!q.isEmpty()) {
                Pair p = q.poll();
                if (isEnemyFind[p.idx]) continue;
                if (nmap[p.x][p.y] == 1) {
                    isEnemyFind[p.idx] = true;
                    if (!isAlreadyFindLocation[p.x][p.y]) {
                        isAlreadyFindLocation[p.x][p.y] = true;
                        alist.add(p);
                        cnt++;
                    }
                    continue;
                }
                if (!isEnemyFind[p.idx]) {
                    visited[p.x][p.y][p.idx] = true;
                    for (int j = 0; j < 3; j++) {
                        int nx = p.x + dx[j];
                        int ny = p.y + dy[j];
                        if (nx >= 0 && nx < n && ny >= 0 && ny < m && !visited[nx][ny][p.idx] && p.depth < d) {
                            q.add(new Pair(nx, ny, p.depth + 1, p.idx));
                        }
                    }
                }
            }
            for (int j = 0; j < alist.size(); j++) {
                Pair p = alist.get(j);
                nmap[p.x][p.y] = 0;
            }
        }
        ans = Math.max(ans, cnt);
    }
 
    public static class Pair {
        int x, y, depth, idx;
 
        public Pair(int x, int y, int depth, int idx) {
            this.x = x;
            this.y = y;
            this.depth = depth;
            this.idx = idx;
        }
    }
}
cs


반응형
반응형

* 핵심 개념

풀어왔던 삼성 기출문제중에서 BFS, DFS 개념이 필요없는 문제

1. 규칙

이전 방향을 Stack에 저장해서(필자는 String에 붙이는 방식 사용) 뒤에서부터 가져다 쓰는 규칙을 찾으면 쉽게 풀림

ex) 1세대를 움직이는데, 이전 이동한 방향이 3, 0 이라면 다음 이동은 1, 0((3+1)%4)이 된다.

2. 0세대

n번째 세대는 2^n번 움직이지만 0세대는 예외로 그냥 움직인다.

이때는 움직임을 저장하지 않고 현재 x,y 좌표만 변경하면 된다.


* 문제

문제

드래곤 커브는 다음과 같은 세 가지 속성으로 이루어져 있으며, 이차원 좌표 평면 위에서 정의된다. 좌표 평면의 x축은 → 방향, y축은 ↓ 방향이다.

  1. 시작 점
  2. 시작 방향
  3. 세대

0세대 드래곤 커브는 아래 그림과 같은 길이가 1인 선분이다. 아래 그림은 (0, 0)에서 시작하고, 시작 방향은 오른쪽인 0세대 드래곤 커브이다.

... 생략

즉, K(K > 1)세대 드래곤 커브는 K-1세대 드래곤 커브를 끝 점을 기준으로 90도 시계 방향 회전 시킨 다음, 그것을 끝 점에 붙인 것이다.

크기가 100×100인 격자 위에 드래곤 커브가 N개 있다. 이때, 크기가 1×1인 정사각형의 네 꼭짓점이 모두 드래곤 커브의 일부인 정사각형의 개수를 구하는 프로그램을 작성하시오. 격자의 좌표는 (x, y)로 나타내며, 0 ≤ x ≤ 100, 0 ≤ y ≤ 100만 유효한 좌표이다.

입력

첫째 줄에 드래곤 커브의 개수 N(1 ≤ N ≤ 20)이 주어진다. 둘째 줄부터 N개의 줄에는 드래곤 커브의 정보가 주어진다. 드래곤 커브의 정보는 네 정수 x, y, d, g로 이루어져 있다. x와 y는 드래곤 커브의 시작 점, d는 시작 방향, g는 세대이다. (0 ≤ x, y ≤ 100, 0 ≤ d ≤ 3, 0 ≤ g ≤ 10)

입력으로 주어지는 드래곤 커브는 격자 밖으로 벗어나지 않는다. 드래곤 커브는 서로 겹칠 수 있다.

방향은 0, 1, 2, 3 중 하나이고, 다음을 의미한다.

  • 0: x좌표가 증가하는 방향 (→)
  • 1: y좌표가 감소하는 방향 (↑)
  • 2: x좌표가 감소하는 방향 (←)
  • 3: y좌표가 증가하는 방향 (↓)

출력

첫째 줄에 크기가 1×1인 정사각형의 네 꼭짓점이 모두 드래곤 커브의 일부인 것의 개수를 출력한다.

* 소스 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import java.io.*;
import java.util.*;
 
public class Main {
    public static StringTokenizer stk;
    public static StringBuilder sb = new StringBuilder();
    public static int[] dx = {10-10};
    public static int[] dy = {0-101};
    public static boolean[][] visited = new boolean[101][101];
 
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int t = Integer.parseInt(br.readLine());
        for (int i = 1; i <= t; i++) {
            stk = new StringTokenizer(br.readLine());
            int x = Integer.parseInt(stk.nextToken());
            int y = Integer.parseInt(stk.nextToken());
            int d = Integer.parseInt(stk.nextToken());
            int g = Integer.parseInt(stk.nextToken());
            String s = String.valueOf(d);
            visited[x][y] = true;
 
            for (int j = 0; j <= g; j++) {
                //1세대마다 2의 제곱으로 늘어남
                if (j != 0 && s.length() >= Math.pow(2, j)) continue;
                for (int k = s.length() - 1; k >= 0; k--) {
                    int nd;
                    if (j == 0) nd = s.charAt(k) - '0';
                    else nd = (s.charAt(k) - '0' + 1) % 4;
 
                    int nx = x + dx[nd];
                    int ny = y + dy[nd];
                    visited[nx][ny] = true;
                    x = nx;
                    y = ny;
                    if (j != 0) s += nd;
                }
            }
        }
        System.out.println(check());
    }
 
    public static int check() {
        int ans = 0;
        for (int i = 0; i < 100; i++) {
            for (int j = 0; j < 100; j++) {
                if (visited[i][j] && visited[i + 1][j] && visited[i][j + 1&& visited[i + 1][j + 1]) ans++;
            }
        }
        return ans;
    }
}
cs


반응형
반응형

* 핵심

1. DFS 를 이용하여 nCr 개의 치킨집을 선택 (순서 상관 x)

# 주의할 점 

nCr을 구할 때 for문을 처음부터 시작하면 시간 초과가 발생한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 시간초과 발생
public static void nCr(int cnt) {
    if (cnt == m) {
        dist();
        return;
    }
    for (int i = 1; i <= chickList.size(); i++) {
        if (!chickVisited[i]) {
            chickVisited[i] = true;
            nCr(i, cnt + 1);
            chickVisited[i] = false;
        }
    }
}
 
// 시간 줄이는 nCr
public static void nCr(int idx, int cnt) {
    if (cnt == m) {
        dist();
        return;
    }
    for (int i = idx; i <= chickList.size(); i++) {
        if (!chickVisited[i]) {
            chickVisited[i] = true;
            nCr(i, cnt + 1);
            chickVisited[i] = false;
        }
    }
}
cs

idx값을 추가로 넘겨주고 for문을 idx부터 시작해야 시간을 단축시킬 수 있다.

2. 선택된 치킨집에서 집까지의 단순 좌표값을 빼서 치킨 거리 저장

선택된 치킨집에서 BFS를 사용하여 풀 수 도 있지만, 시간 초과 발생    -> map에 장애물이 없으므로 BFS보단 좌표계산이 훨씬 빠름

* 문제

문제

크기가 N×N인 도시가 있다. 도시는 1×1크기의 칸으로 나누어져 있다. 도시의 각 칸은 빈 칸, 치킨집, 집 중 하나이다. 도시의 칸은 (r, c)와 같은 형태로 나타내고, r행 c열 또는 위에서부터 r번째 칸, 왼쪽에서부터 c번째 칸을 의미한다. r과 c는 1부터 시작한다.

이 도시에 사는 사람들은 치킨을 매우 좋아한다. 따라서, 사람들은 "치킨 거리"라는 말을 주로 사용한다. 치킨 거리는 집과 가장 가까운 치킨집 사이의 거리이다. 즉, 치킨 거리는 집을 기준으로 정해지며, 각각의 집은 치킨 거리를 가지고 있다. 도시의 치킨 거리는 모든 집의 치킨 거리의 합이다.

임의의 두 칸 (r1, c1)과 (r2, c2) 사이의 거리는 |r1-r2| + |c1-c2|로 구한다.

예를 들어, 아래와 같은 지도를 갖는 도시를 살펴보자.

0 2 0 1 0
1 0 1 0 0
0 0 0 0 0
0 0 0 1 1
0 0 0 1 2

0은 빈 칸, 1은 집, 2는 치킨집이다.

(2, 1)에 있는 집과 (1, 2)에 있는 치킨집과의 거리는 |2-1| + |1-2| = 2, (5, 5)에 있는 치킨집과의 거리는 |2-5| + |1-5| = 7이다. 따라서, (2, 1)에 있는 집의 치킨 거리는 2이다.

(5, 4)에 있는 집과 (1, 2)에 있는 치킨집과의 거리는 |5-1| + |4-2| = 6, (5, 5)에 있는 치킨집과의 거리는 |5-5| + |4-5| = 1이다. 따라서, (5, 4)에 있는 집의 치킨 거리는 1이다.

이 도시에 있는 치킨집은 모두 같은 프랜차이즈이다. 프렌차이즈 본사에서는 수익을 증가시키기 위해 일부 치킨집을 폐업시키려고 한다. 오랜 연구 끝에 이 도시에서 가장 수익을 많이 낼 수 있는  치킨집의 개수는 최대 M개라는 사실을 알아내었다.

도시에 있는 치킨집 중에서 최대 M개를 고르고, 나머지 치킨집은 모두 폐업시켜야 한다. 어떻게 고르면, 도시의 치킨 거리가 가장 작게 될지 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N(2 ≤ N ≤ 50)과 M(1 ≤ M ≤ 13)이 주어진다.

둘째 줄부터 N개의 줄에는 도시의 정보가 주어진다.

도시의 정보는 0, 1, 2로 이루어져 있고, 0은 빈 칸, 1은 집, 2는 치킨집을 의미한다. 집의 개수는 2N개를 넘지 않으며, 적어도 1개는 존재한다. 치킨집의 개수는 M보다 크거나 같고, 13보다 작거나 같다.

출력

첫째 줄에 폐업시키지 않을 치킨집을 최대 M개를 골랐을 때, 도시의 치킨 거리의 최솟값을 출력한다.


* 소스 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import java.io.*;
import java.util.*;
 
public class Main {
    public static StringTokenizer stk;
    public static StringBuilder sb = new StringBuilder();
    public static int n, m, ans = Integer.MAX_VALUE;
    public static int[][] map;  //지도
    public static boolean[] chickVisited;       //nCr에서 방문한 치킨집
    public static ArrayList<Pair> homeList = new ArrayList<>();     //집 위치
    public static ArrayList<Pair> chickList = new ArrayList<>();    //치킨집 위치
 
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        stk = new StringTokenizer(br.readLine());
        n = Integer.parseInt(stk.nextToken());
        m = Integer.parseInt(stk.nextToken());
        map = new int[n + 1][n + 1];
        for (int i = 1; i <= n; i++) {
            stk = new StringTokenizer(br.readLine());
            for (int j = 1; j <= n; j++) {
                map[i][j] = Integer.parseInt(stk.nextToken());
                if (map[i][j] == 1) {
                    homeList.add(new Pair(i, j));
                } else if (map[i][j] == 2) {
                    chickList.add(new Pair(i, j));
                }
            }
        }
        chickVisited = new boolean[chickList.size() + 1];
 
        nCr(10);
        System.out.println(ans);
    }
 
    public static void nCr(int idx, int cnt) {
        if (cnt == m) {
            dist();
            return;
        }
        for (int i = idx; i <= chickList.size(); i++) {
            if (!chickVisited[i]) {
                chickVisited[i] = true;
                nCr(i, cnt + 1);
                chickVisited[i] = false;
            }
        }
    }
 
    public static void dist() {
        int sum = 0;
        for (int i = 0; i < homeList.size(); i++) {
            Pair homeInfo = homeList.get(i);
            int tmp = Integer.MAX_VALUE;
            for (int j = 1; j < chickVisited.length; j++) {
                if (chickVisited[j]) {
                    Pair chickInfo = chickList.get(j - 1);
                    int chickDist = Math.abs(homeInfo.x - chickInfo.x) + Math.abs(homeInfo.y - chickInfo.y);
                    tmp = Math.min(tmp, chickDist);
                }
            }
            sum += tmp;
        }
        ans = Math.min(ans, sum);
    }
 
    public static class Pair {
        int x, y;
 
        public Pair(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
}
cs


반응형
반응형

* 핵심

삼성기출중 쉬운편에 속하는 문제이다.

2중 For문을 돌면서 BFS로 탐색하면 된다.

문제에서 설명이 조금 애매한데, 몇 번이라기보다는 몇 일이 걸린다고 이해하는게 편하다.

하루에 인구 이동한 나라는 그 날은 또 다른 나라랑 연합을 맺을 수 없다.

문제에 주어진 마지막 예시를 보면,

1일차

2일차

3일차

위 예제를 보면 6번의 인구 이동이 있지만 3일에 걸쳐 인구 이동되었기 때문에 3을 출력하는 것이 맞다.

* 문제

문제

N×N크기의 땅이 있고, 땅은 1×1개의 칸으로 나누어져 있다. 각각의 땅에는 나라가 하나씩 존재하며, r행 c열에 있는 나라에는 A[r][c]명이 살고 있다. 인접한 나라 사이에는 국경선이 존재한다. 모든 나라는 1×1 크기이기 때문에, 모든 국경선은 정사각형 형태이다.

오늘부터 인구 이동이 시작되는 날이다.

인구 이동은 다음과 같이 진행되고, 더 이상 아래 방법에 의해 인구 이동이 없을 때까지 지속된다.

  • 국경선을 공유하는 두 나라의 인구 차이가 L명 이상, R명 이하라면, 두 나라가 공유하는 국경선을 오늘 하루동안 연다.
  • 위의 조건에 의해 열어야하는 국경선이 모두 열렸다면, 인구 이동을 시작한다.
  • 국경선이 열려있어 인접한 칸만을 이용해 이동할 수 있으면, 그 나라를 오늘 하루 동안은 연합이라고 한다.
  • 연합을 이루고 있는 각 칸의 인구수는 (연합의 인구수) / (연합을 이루고 있는 칸의 개수)가 된다. 편의상 소수점은 버린다.
  • 연합을 해체하고, 모든 국경선을 닫는다.

각 나라의 인구수가 주어졌을 때, 인구 이동이 몇 번 발생하는지 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N, L, R이 주어진다. (1 ≤ N ≤ 50, 1 ≤ L ≤ R ≤ 100)

둘째 줄부터 N개의 줄에 각 나라의 인구수가 주어진다. r행 c열에 주어지는 정수는 A[r][c]의 값이다. (1 ≤ A[r][c] ≤ 100)

인구 이동이 발생하는 횟수가 2,000번 보다 작거나 같은 입력만 주어진다.

출력

인구 이동이 몇 번 발생하는지 첫째 줄에 출력한다.

* 소스 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import java.io.*;
import java.util.*;
 
public class Main {
    public static StringTokenizer stk;
    public static StringBuilder sb = new StringBuilder();
    public static int[] dx = {1-100};
    public static int[] dy = {001-1};
    public static int min, max, ans = 0;
 
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        stk = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(stk.nextToken());
        min = Integer.parseInt(stk.nextToken());
        max = Integer.parseInt(stk.nextToken());
        int[][] map = new int[n + 1][n + 1];
        boolean[][] visited = new boolean[n + 1][n + 1];
        for (int i = 1; i <= n; i++) {
            stk = new StringTokenizer(br.readLine());
            for (int j = 1; j <= n; j++) {
                map[i][j] = Integer.parseInt(stk.nextToken());
            }
        }
        boolean hasMoreMoving = false;  //while문에서 인구이동 발생 여부 확인
        while (!hasMoreMoving) {
            Queue<Pair> q = new LinkedList<>();
            int sum = 0, cnt = 0;                           //sum = 인구 이동할 나라의 사람수, cnt = 인구 이동한 나라 개수
            ArrayList<Pair> alist = new ArrayList<>();      //해당 점을 방문했을 때 이동한 점의 좌표 정보
            for (int i = 1; i <= n; i++) {
                for (int j = 1; j <= n; j++) {
                    q.add(new Pair(i, j));
                    while (!q.isEmpty()) {
                        Pair p = q.poll();
                        if (!visited[p.x][p.y]) {
                            visited[p.x][p.y] = true;
                            cnt++;
                            sum += map[p.x][p.y];
                            alist.add(new Pair(p.x, p.y));
                            for (int k = 0; k < 4; k++) {
                                int nx = p.x + dx[k];
                                int ny = p.y + dy[k];
                                if (nx > 0 && nx <= n && ny > 0 && ny <= n && !visited[nx][ny] && checkPerson(map[p.x][p.y], map[nx][ny])) {
                                    q.add(new Pair(nx, ny));
                                }
                            }
                        }
                    }
 
                    //방문한 좌표가 인구 이동이 필요한 경우
                    if (alist.size() > 1) {
                        int movingSum = sum / cnt;
                        for (int k = 0; k < alist.size(); k++) {
                            Pair p = alist.get(k);
                            map[p.x][p.y] = movingSum;
                        }
                        hasMoreMoving = true;
                    }
                    //매 시작점마다 초기화
                    alist = new ArrayList<>();
                    sum = 0;
                    cnt = 0;
                }
            }   //end of for
            if (hasMoreMoving) {        //더이상 움직일 게 있다면 visited배열 초기화
                ans++;
                hasMoreMoving = false;
                visited = new boolean[n + 1][n + 1];
            } else break;
        }
        System.out.println(ans);
    }
 
    //현재 인구와 이동할 인구의 차이 계산
    public static boolean checkPerson(int curCnt, int nextCnt) {
        int absCnt = Math.abs(curCnt - nextCnt);
        if (absCnt >= min && absCnt <= max) return true;
        else return false;
    }
 
    public static class Pair {
        int x, y;
 
        public Pair(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
}
cs


반응형
반응형

* 핵심 개념

삽질을 상당히 많이한 문제다.

문제를 해석하면 한 방향으로 기울이면 끝까지 이동해야하고 두 구슬이 같은 위치인 경우도 처리가 필요하다.

4%에서 계속 틀렸다고 나왔는데 이유를 알고보니 개념의 실수였다.

구슬의 방문여부를 4차원 배열로 해결해야 하는데, 2차원배열 2개로 해결하려다 보니 당연히 틀릴수밖에 없었다.

이 둘의 차이는 명백하니 실수하지 말아야겠다.


문제 해결 순서는 

빨간 구슬을 한 방향 끝까지 이동 >> 파란 구슬을 한 방향 끝까지 이동 >> 둘이 같은 점이면 많이 움직인 구슬을 한칸 되돌림 >> 새로운 점 큐에 추가


* 문제

문제

스타트링크에서 판매하는 어린이용 장난감 중에서 가장 인기가 많은 제품은 구슬 탈출이다. 구슬 탈출은 직사각형 보드에 빨간 구슬과 파란 구슬을 하나씩 넣은 다음, 빨간 구슬을 구멍을 통해 빼내는 게임이다.

보드의 세로 크기는 N, 가로 크기는 M이고, 편의상 1×1크기의 칸으로 나누어져 있다. 가장 바깥 행과 열은 모두 막혀져 있고, 보드에는 구멍이 하나 있다. 빨간 구슬과 파란 구슬의 크기는 보드에서 1×1크기의 칸을 가득 채우는 사이즈이고, 각각 하나씩 들어가 있다. 게임의 목표는 빨간 구슬을 구멍을 통해서 빼내는 것이다. 이때, 파란 구슬이 구멍에 들어가면 안된다.

이때, 구슬을 손으로 건드릴 수는 없고, 중력을 이용해서 이리 저리 굴려야 한다. 왼쪽으로 기울이기, 오른쪽으로 기울이기, 위쪽으로 기울이기, 아래쪽으로 기울이기와 같은 네 가지 동작이 가능하다.

각각의 동작에서 공은 동시에 움직인다. 빨간 구슬이 구멍에 빠지면 성공이지만, 파란 구슬이 구멍에 빠지면 실패이다. 빨간 구슬과 파란 구슬이 동시에 구멍에 빠져도 실패이다. 빨간 구슬과 파란 구슬은 동시에 같은 칸에 있을 수 없다. 또, 빨간 구슬과 파란 구슬의 크기는 한 칸을 모두 차지한다. 기울이는 동작을 그만하는 것은 더 이상 구슬이 움직이지 않을 때 까지이다.

보드의 상태가 주어졌을 때, 최소 몇 번 만에 빨간 구슬을 구멍을 통해 빼낼 수 있는지 구하는 프로그램을 작성하시오.

입력

첫 번째 줄에는 보드의 세로, 가로 크기를 의미하는 두 정수 N, M (3 ≤ N, M ≤ 10)이 주어진다. 다음 N개의 줄에 보드의 모양을 나타내는 길이 M의 문자열이 주어진다. 이 문자열은 '.', '#', 'O', 'R', 'B' 로 이루어져 있다. '.'은 빈 칸을 의미하고, '#'은 공이 이동할 수 없는 장애물 또는 벽을 의미하며, 'O'는 구멍의 위치를 의미한다. 'R'은 빨간 구슬의 위치, 'B'는 파란 구슬의 위치이다.

입력되는 모든 보드의 가장자리에는 모두 '#'이 있다. 구멍의 개수는 한 개 이며, 빨간 구슬과 파란 구슬은 항상 1개가 주어진다.

출력

최소 몇 번 만에 빨간 구슬을 구멍을 통해 빼낼 수 있는지 출력한다. 만약, 10번 이하로 움직여서 빨간 구슬을 구멍을 통해 빼낼 수 없으면 -1을 출력한다.


* 소스 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import java.io.*;
import java.util.*;
 
public class Main {
    public static StringTokenizer stk;
    public static StringBuilder sb = new StringBuilder();
    public static int[] dx = {1-100};
    public static int[] dy = {001-1};
    public static int n, m;
    public static int rx, ry, bx, by;
    public static int[][] map;
    public static boolean[][][][] visited;
    public static Queue<Pair> q = new LinkedList<>();
 
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        stk = new StringTokenizer(br.readLine());
        n = Integer.parseInt(stk.nextToken());
        m = Integer.parseInt(stk.nextToken());
        map = new int[n + 1][m + 1];
        visited = new boolean[n + 1][m + 1][n + 1][m + 1];
        for (int i = 1; i <= n; i++) {
            String s = br.readLine();
            for (int j = 1; j <= s.length(); j++) {
                int tmp = 0;
                switch (s.charAt(j - 1)) {
                    case '#':
                        tmp = -1;
                        break;
                    case '.':
                        tmp = 0;
                        break;
                    case 'R':
                        tmp = 1;
                        rx = i;
                        ry = j;
                        break;
                    case 'B':
                        tmp = 2;
                        bx = i;
                        by = j;
                        break;
                    case 'O':
                        tmp = 3;
                        break;
                }
                map[i][j] = tmp;
            }
        }
        System.out.println(bfs());
    }
 
    public static int bfs() {
        q.add(new Pair(rx, ry, bx, by, 0));
        visited[rx][ry][bx][by] = true;
        while (!q.isEmpty()) {
            Pair p = q.poll();
            if (p.cnt > 10continue;
            if (map[p.bx][p.by] == 3continue;
            if (map[p.rx][p.ry] == 3return p.cnt;
 
            for (int i = 0; i < 4; i++) {
                //빨간 구슬 상하좌우 끝까지 이동
                int next_rx = p.rx, next_ry = p.ry;
                while (true) {
                    //다음 지점이 벽이랑 구멍이 아니면
                    if (map[next_rx][next_ry] != -1 && map[next_rx][next_ry] != 3) {
                        next_rx += dx[i];
                        next_ry += dy[i];
                    } else {
                        //다음 지점이 벽이면
                        if (map[next_rx][next_ry] == -1) {
                            next_rx -= dx[i];
                            next_ry -= dy[i];
                        }
                        break;
                    }
                }
 
                //파란 구슬 상하좌우 끝까지 이동
                int next_bx = p.bx, next_by = p.by;
                while (true) {
                    //다음 지점이 벽이랑 구멍이 아니면
                    if (map[next_bx][next_by] != -1 && map[next_bx][next_by] != 3) {
                        next_bx += dx[i];
                        next_by += dy[i];
                    } else {
                        //다음 지점이 벽이면
                        if (map[next_bx][next_by] == -1) {
                            next_bx -= dx[i];
                            next_by -= dy[i];
                        }
                        break;
                    }
                }
 
                //구한 Red, Blue 의 점이 서로 같은데 'O'가 아닌 경우
                //더 움직인 구슬의 dx[i], dy[i]를 빼준다
                if (next_rx == next_bx && next_ry == next_by) {
                    if (map[next_rx][next_ry] != 3) {
                        int red_cost = Math.abs(next_rx - p.rx) + Math.abs(next_ry - p.ry);
                        int blue_cost = Math.abs(next_bx - p.bx) + Math.abs(next_by - p.by);
                        if (red_cost > blue_cost) {
                            next_rx -= dx[i];
                            next_ry -= dy[i];
                        } else {
                            next_bx -= dx[i];
                            next_by -= dy[i];
                        }
                    }
                }
 
                //next 점이 방문한적 없다면 큐에 추가
                if (!visited[next_rx][next_ry][next_bx][next_by]) {
                    visited[next_rx][next_ry][next_bx][next_by] = true;
                    q.add(new Pair(next_rx, next_ry, next_bx, next_by, p.cnt + 1));
                }
            }
        }
        return -1;
    }
 
    public static class Pair {
        int rx, ry, bx, by, cnt;
 
        public Pair(int rx, int ry, int bx, int by, int cnt) {
            this.rx = rx;
            this.ry = ry;
            this.bx = bx;
            this.by = by;
            this.cnt = cnt;
        }
    }
}
cs


반응형