문제
지금 민식이가 계획한 여행은 달이 맨 처음 뜨기 시작할 때 부터, 준비했던 여행길이다. 하지만, 매번 달이 차오를 때마다 민식이는 어쩔 수 없는 현실의 벽 앞에서 다짐을 포기하고 말았다.
민식이는 매번 자신의 다짐을 말하려고 노력했지만, 말을 하면 아무도 못 알아들을 것만 같아서, 지레 겁먹고 벙어리가 되어버렸다. 결국 민식이는 모두 잠든 새벽 네시 반쯤 홀로 일어나, 창 밖에 떠있는 달을 보았다.
하루밖에 남지 않았다. 달은 내일이면 다 차오른다. 이번이 마지막기회다. 이걸 놓치면 영영 못간다.
영식이는 민식이가 오늘도 여태것처럼 그냥 잠 들어버려서 못 갈지도 모른다고 생각했다. 하지만 그러기엔 민식이의 눈에는 저기 뜬 달이 너무나 떨렸다.
민식이는 지금 미로 속에 있다. 미로는 직사각형 모양이고, 여행길을 떠나기 위해 미로를 탈출하려고 한다. 미로는 다음과 같이 구성되어져있다.
- 빈 곳 : 언제나 이동할 수 있다. ('.‘로 표시됨)
- 벽 : 절대 이동할 수 없다. (‘#’)
- 열쇠 : 언제나 이동할 수 있다. 이 곳에 처음 들어가면 열쇠를 집는다. (a - f)
- 문 : 대응하는 열쇠가 있을 때만 이동할 수 있다. (A - F)
- 민식이의 현재 위치 : 빈 곳이고, 민식이가 현재 서 있는 곳이다. (숫자 0)
- 출구 : 달이 차오르기 때문에, 민식이가 가야하는 곳이다. 이 곳에 오면 미로를 탈출한다. (숫자 1)
달이 차오르는 기회를 놓치지 않기 위해서, 미로를 탈출하려고 한다. 한 번의 움직임은 현재 위치에서 수평이나 수직으로 한 칸 이동하는 것이다.
민식이가 미로를 탈출하는데 걸리는 이동 횟수의 최솟값을 구하는 프로그램을 작성하시오.
입력
첫째 줄에 미로의 세로 크기 N과 가로 크기 M이 주어진다. (1 ≤ N, M ≤ 50) 둘째 줄부터 N개의 줄에 미로의 모양이 주어진다. 같은 타입의 열쇠가 여러 개 있을 수 있고, 문도 마찬가지이다. 그리고, 영식이가 열쇠를 숨겨놓는 다면 문에 대응하는 열쇠가 없을 수도 있다. 0은 한 개, 1은 적어도 한 개 있다. 그리고, 열쇠는 여러 번 사용할 수 있다.
출력
첫째 줄에 민식이가 미로를 탈출하는데 드는 이동 횟수의 최솟값을 출력한다. 만약 민식이가 미로를 탈출 할 수 없으면, -1을 출력한다.
예제 입력 1 복사
1 7 f0.F..1
예제 출력 1 복사
7
전형적인 탈출하기 bfs 문제이다.
문제는 지도에서 문과 열쇠가 등장하는데 열쇠가 있어야만 문을 열 수 있다는 게 이 문제의 특이한 점이다.
열쇠 소유 여부를 기록하기 위해 벡터를 써봤는데, 결국 방문 여부를 기록하는 visit 배열에 좌표 뿐만 아니라 열쇠 소유 여부까지 기록을 해야해서 비트마스크를 사용했다.
지도는 int 로 다루는 게 간단해서 모든 값을 int로 변환했다. 0은 이동가능, 1은 탈출지점, 2는 벽, 10~15는 각각 A, B, C...F에 대응되고 100~105는 a~f에 대응된다. 여기에 비트 마스크를 이용해 각 인덱스별로 a~f 열쇠에 대응되게 했다.
즉 000100 이라면 세번째 열쇠만 있으니 c 열쇠가 있다는 뜻이다.
이런식으로 열쇠가 있을때에만 문을 열고 넘어갈 수 있음을 항상 체크하고 bfs를 돌리면 답을 구할 수 있다.
chosh95/STUDY
프로그래밍 문제 및 알고리즘 정리. Contribute to chosh95/STUDY development by creating an account on GitHub.
github.com
C++ 코드
#include <iostream>
#include <queue>
#include <vector>
#include <algorithm>
#include <memory.h>
using namespace std;
int N, M;
int p[51][51]; // 0 : 이동가능, 1 : 탈출구, 2 : 벽, 10~15 : A~F, 100~105 : a~f
int visit[51][51][1<<6];
int dx[4] = { -1,0,0,1 };
int dy[4] = { 0,-1,1,0 };
int sx, sy; //시작 위치
int res = 987654321; // 결과값
struct pos {
int x, y, dist; // 좌표, 이동거리
int key = 0; // 열쇠 소유 여부 [A,B, ... , F]
};
void bfs() {
memset(visit, -1, sizeof(visit));
queue<pos> q;
q.push({ sx,sy,0, 0 });
visit[sx][sy][0] = 0;
while (!q.empty()) {
pos cur = q.front();
q.pop();
if (p[cur.x][cur.y] == 1) {
res = min(res, visit[cur.x][cur.y][cur.key]);
return;
}
for (int i = 0; i < 4; i++) {
int nx = cur.x + dx[i];
int ny = cur.y + dy[i];
if (nx<1 || ny<1 || nx>N || ny>M) continue; // 범위 벗어나면 continue
if (p[nx][ny] == 2) continue; // 벽이면 건너갈 수 없다.
if (p[nx][ny] == 0 || p[nx][ny] == 1) {
if (visit[nx][ny][cur.key] != -1) continue;
q.push({ nx,ny,cur.dist + 1,cur.key });
visit[nx][ny][cur.key] = cur.dist + 1;
}
else if (p[nx][ny] >= 10 && p[nx][ny] <= 15) { // 문
int needKey = 1<<(p[nx][ny] - 10); // 문을 여는데 필요한 열쇠 인덱스
if ((cur.key & needKey) == 0) continue; //열쇠 없으면 못간다.
if (visit[nx][ny][cur.key | needKey] != -1) continue; // 이미 방문했으면 continue
q.push({ nx,ny,cur.dist + 1,cur.key|needKey });
visit[nx][ny][cur.key | needKey] = cur.dist + 1;
}
else { // 열쇠
int nextKey = 1<<(p[nx][ny] - 100);
if (visit[nx][ny][cur.key | nextKey] != -1) continue;
q.push({ nx,ny,cur.dist + 1,cur.key|nextKey });
visit[nx][ny][cur.key|nextKey] = cur.dist + 1;
}
}
}
}
int main()
{
cin >> N >> M;
string str;
for (int i = 1; i <= N; i++) {
cin >> str;
for (int j = 1; j <= M; j++) {
char cur = str[j - 1];
if (cur == '.')
p[i][j] = 0;
else if (cur == '#')
p[i][j] = 2;
else if (cur == '0') {
sx = i, sy = j;
p[i][j] = 0;
}
else if (cur == '1') {
p[i][j] = 1;
}
else if (cur >= 'A' && cur <= 'F')
p[i][j] = cur - 'A' + 10;
else
p[i][j] = cur - 'a' + 100;
}
}
bfs();
if (res == 987654321)
cout << -1;
else
cout << res;
}
'알고리즘 > 백준' 카테고리의 다른 글
[백준] 소형기관차 (2616번) (0) | 2020.08.26 |
---|---|
[백준] N번째 큰 수 (2075번) (0) | 2020.08.26 |
[백준] 소수&팰린드롬 (1747번) (0) | 2020.08.24 |
[백준] 생태학 (4358번) (0) | 2020.08.24 |
[백준] 부분 문자열 (16916번) (0) | 2020.08.24 |