문제
보드의 세로 크기는 N, 가로 크기는 M이고, 편의상 1×1크기의 칸으로 나누어져 있다. 가장 바깥 행과 열은 모두 막혀져 있고, 보드에는 구멍이 하나 있다. 빨간 구슬과 파란 구슬의 크기는 보드에서 1×1크기의 칸을 가득 채우는 사이즈이고, 각각 하나씩 들어가 있다. 게임의 목표는 빨간 구슬을 구멍을 통해서 빼내는 것이다. 이때, 파란 구슬이 구멍에 들어가면 안 된다.
이때, 구슬을 손으로 건드릴 수는 없고, 중력을 이용해서 이리 저리 굴려야 한다. 왼쪽으로 기울이기, 오른쪽으로 기울이기, 위쪽으로 기울이기, 아래쪽으로 기울이기와 같은 네 가지 동작이 가능하다.
각각의 동작에서 공은 동시에 움직인다. 빨간 구슬이 구멍에 빠지면 성공이지만, 파란 구슬이 구멍에 빠지면 실패이다. 빨간 구슬과 파란 구슬이 동시에 구멍에 빠져도 실패이다. 빨간 구슬과 파란 구슬은 동시에 같은 칸에 있을 수 없다. 또, 빨간 구슬과 파란 구슬의 크기는 한 칸을 모두 차지한다. 기울이는 동작을 그만하는 것은 더 이상 구슬이 움직이지 않을 때 까지이다.
보드의 상태가 주어졌을 때, 최소 몇 번 만에 빨간 구슬을 구멍을 통해 빼낼 수 있는지 구하는 프로그램을 작성하시오.
문제조건
첫 번째 줄에는 보드의 세로, 가로 크기를 의미하는 두 정수 N, M (3 ≤ N, M ≤ 10)이 주어진다. 다음 N개의 줄에 보드의 모양을 나타내는 길이 M의 문자열이 주어진다. 이 문자열은 '.', '#', 'O', 'R', 'B' 로 이루어져 있다. '.'은 빈 칸을 의미하고, '#'은 공이 이동할 수 없는 장애물 또는 벽을 의미하며, 'O'는 구멍의 위치를 의미한다. 'R'은 빨간 구슬의 위치, 'B'는 파란 구슬의 위치이다.
입력되는 모든 보드의 가장자리에는 모두 '#'이 있다. 구멍의 개수는 한 개 이며, 빨간 구슬과 파란 구슬은 항상 1개가 주어진다.
풀이
문제의 호흡이 생각보다 길었다. 상하좌우로 이동해가며 모든 경우의 수를 시도해 보아야 함으로 dfs를 사용하였다. 구슬이 움직일때 맞이하는 경우는 아래와 같이 4가지이다.
1. 빈 칸(.)
2. 장애물 또는 벽(#)
3. 다른 색 구슬
4. 구멍
1의 경우에는 그 방향으로 계속해서 움직일 수 있음을 뜻한다. 주어진 방향으로 계속 움직이면 된다.
2의 경우에는 멈춰야 하는 경우이다. 구슬이 움직이는 것을 멈추고 벽의 전칸에 머물게 된다. (swap을 사용하여 이전 구슬의 위치와 멈춰야 하는 구슬의 위치를 변경해 주었다)
3의 경우가 가장 까다로웠다. 나는 빨간색 구슬을 먼저 움직인 후 파란색 구슬을 움직일 것이기 때문에 파란색 구슬 앞에 빨간색 구슬이 있는 것은 더이상 움직이지 못함을 뜻한다(벽과 같은 의미). 하지만 빨간색 구슬 앞에 파란색 구슬이 있다면 파란색 구슬을 움직인 후 다시 한 번 빨간색 구슬을 움직일 수 있는지를 확인해 주어야 한다.
빨간색만 구멍에 들어가는 경우를 찾아야 하기 때문에 파란색이 구멍에 들어가는 경우, 이는 잘못된 이동 방향이다. 따라서 이동 이전의 값으로 원상복귀 시켜 주었다.
코드
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
using namespace std;
vector<vector<char>> board;
int n, m;
int ans = 11;
//상,하,좌,우 이동
int dx[] = { -1,1,0,0 };
int dy[] = { 0,0,1,-1 };
void move(int rx, int ry, int bx, int by, int cnt) {
if (cnt >= ans) { return; }
for (int i = 0; i < 4; i++) {
int rnx = rx + dx[i];
int rny = ry + dy[i];
int bnx = bx + dx[i];
int bny = by + dy[i];
bool redHole = false;
bool blueHole = false;
bool BR = false;
vector<vector<char>> save;
save = board;
//빨간 구슬 이동
while (board[rnx][rny] == '.') {
rnx += dx[i];
rny += dy[i];
}
if (board[rnx][rny] == '#') {
// 이동할 수 있을만큼 이동 -> 실제로 이동시킴
swap(board[rnx - dx[i]][rny - dy[i]], board[rx][ry]);
}
else if (board[rnx][rny] == 'B') {
BR = true;
}
else {
// hole
redHole = true;
board[rx][ry] = '.';
}
//파란 구슬 이동
while (board[bnx][bny] == '.') {
bnx += dx[i];
bny += dy[i];
}
if (board[bnx][bny] == '#'|| board[bnx][bny] == 'R') {
// 이동할 수 있을만큼 이동 -> 실제로 이동시킴
swap(board[bnx - dx[i]][bny - dy[i]], board[bx][by]);
}
else {
// hole
blueHole = true;
board[bx][by] = '.';
}
//움직이는 방향에 파란 구슬이 빨간 구슬보다 앞에 있다면
//파란 구슬을 움직이고 빨간 구슬을 다시 움직여야
if (BR) {
while (board[rnx][rny] == '.') {
rnx += dx[i];
rny += dy[i];
}
if (board[rnx][rny] == '#'|| board[rnx][rny] == 'B') {
// 이동할 수 있을만큼 이동 -> 실제로 이동시킴
swap(board[rnx - dx[i]][rny - dy[i]], board[rx][ry]);
}
else {
// hole
redHole = true;
board[rx][ry] = '.';
}
BR = false;
}
if (redHole && blueHole) {
// 빨간색과 파란색이 모두 구멍에 들어감 -> X
board = save;
}
else if (redHole) {
board = save;
ans = min(cnt, ans);
return;
}
else if (blueHole) {
board = save;
continue;
}
else if (rnx - dx[i] == rx && rny - dy[i] == ry && bnx - dx[i] == bx && bny - dy[i] == by) {
//이동이 없음
continue;
}
else {
move(rnx - dx[i], rny - dy[i], bnx - dx[i], bny - dy[i], cnt+1);
board = save;
}
}
}
int main() {
cin >> n >> m;
int rx, ry, bx, by;
//board 입력
for (int i = 0; i < n; i++) {
vector <char> v;
board.push_back(v);
for (int j = 0; j < m; j++) {
char c;
cin >> c;
if (c == 'R') { rx = i; ry = j; }
else if (c == 'B') { bx = i; by = j; }
board[i].push_back(c);
}
}
move(rx, ry, bx, by, 1);
if (ans == 11) cout<< -1;
else cout<<ans;
}
'알고리즘 > BOJ' 카테고리의 다른 글
[백준] 9251번: LCS 2 (c++) (0) | 2021.09.16 |
---|---|
[백준] 1806번: 부분합 (c++) (0) | 2021.09.15 |
[백준] 2263번: 트리의 순회 (c++) (0) | 2021.09.01 |
[백준] 12852번: 1로 만들기 2 (c++) (0) | 2021.08.30 |
[백준] 1005번: ACM Craft (c++) (0) | 2021.08.27 |