백준 1194번 (달이 차오른다, 가자., C++, BFS, 비트 마스킹) / 추가 반례 [BAEKJOON]

문제

https://www.acmicpc.net/problem/1194

시간 제한 메모리 제한 제출 정답 맞힌 사람 정답 비율
2 초 128 MB 13253 5293 3586 37.393%

지금 민식이가 계획한 여행은 달이 맨 처음 뜨기 시작할 때 부터, 준비했던 여행길이다. 하지만, 매번 달이 차오를 때마다 민식이는 어쩔 수 없는 현실의 벽 앞에서 다짐을 포기하고 말았다. 매번 자신의 다짐을 말하려고 노력했지만, 말을 하면 아무도 못 알아들을 것만 같아서, 지레 겁먹고 벙어리가 되어버렸다. 결국 민식이는 모두 잠든 새벽 네시 반쯤 홀로 일어나, 창 밖에 떠있는 달을 보았다. 하루밖에 남지 않았다. 달은 내일이면 다 차오른다. 이번이 마지막 기회다. 이걸 놓치면 영영 못 간다. 영식이는 민식이가 오늘도 여태 것처럼 그냥 잠들어버려서 못 갈지도 모른다고 생각했다. 하지만 그러기엔 민식이의 눈에는 저기 뜬 달이 너무나 떨렸다. 미로는 직사각형 모양이고, 여행길을 떠나기 위해 미로를 탈출하려고 한다. 미로는 다음과 같이 구성 되어져 있다.

  • 빈 칸: 언제나 이동할 수 있다. ('.')
  • 벽: 절대 이동할 수 없다. ('#')
  • 열쇠: 언제나 이동할 수 있다. 이 곳에 처음 들어가면 열쇠를 집는다. ('a', 'b', 'c', 'd', 'e', 'f')
  • 문: 대응하는 열쇠가 있을 때만 이동할 수 있다. ('A', 'B', 'C', 'D', 'E', 'F')
  • 민식이의 현재 위치: 빈 곳이고, 민식이가 현재 서 있는 곳이다. ('0')
  • 출구: 달이 차오르기 때문에, 민식이가 가야하는 곳이다. 이 곳에 오면 미로를 탈출한다. ('1')

달이 차오르는 기회를 놓치지 않기 위해서, 미로를 탈출하려고 한다. 한 번의 움직임은 현재 위치에서 수평이나 수직으로 한 칸 이동하는 것이다. 민식이가 미로를 탈출하는데 걸리는 이동 횟수의 최솟값을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 미로의 세로 크기 N과 가로 크기 M이 주어진다. (1 ≤ N, M ≤ 50) 둘째 줄부터 N개의 줄에 미로의 모양이 주어진다. 같은 타입의 열쇠가 여러 개 있을 수 있고, 문도 마찬가지이다. 그리고, 문에 대응하는 열쇠가 없을 수도 있다. '0'은 한 개, '1'은 적어도 한 개 있다. 열쇠는 여러 번 사용할 수 있다.

출력

첫째 줄에 민식이가 미로를 탈출하는데 드는 이동 횟수의 최솟값을 출력한다. 만약 민식이가 미로를 탈출 할 수 없으면, -1을 출력한다.

예제 입력 1

plaintext
1 7
f0.F..1

예제 출력 1

plaintext
7

예제 입력 2

plaintext
5 5
....1
#1###
.1.#0
....A
.1.#.

예제 출력 2

plaintext
-1

예제 입력 3

plaintext
7 8
a#c#eF.1
.#.#.#..
.#B#D###
0....F.1
C#E#A###
.#.#.#..
d#f#bF.1

예제 출력 3

plaintext
55

예제 입력 4

plaintext
3 4
1..0
###.
1...

예제 출력 4

plaintext
3

예제 입력 5

plaintext
3 5
..0..
.###.
..1.A

예제 출력 5

plaintext
6

예제 입력 6

plaintext
4 5
0....
.#B#A
.#.#.
b#a#1

예제 출력 6

plaintext
19

예제 입력 7

plaintext
1 11
c.0.C.C.C.1

예제 출력 7

plaintext
12

예제 입력 8

plaintext
3 6
###...
#0A.1a
###...

예제 출력 8

plaintext
-1

알고리즘 분류

출처


추가 반례 https://www.acmicpc.net/board/view/23445

예제 입력 A

plaintext
2 7
1F.f#.0
A...#.a

예제 출력 A

plaintext
-1

예제 입력 B

plaintext
2 7
1F.f..0
A.....a

예제 출력 B

plaintext
6

예제 입력 C

plaintext
2 7
1F.f#.0
A...#.a

예제 출력 C

plaintext
-1

예제 입력 D

plaintext
8 14
1FD...b#....f#
AC.....#.....#
#.....##.....#
#cd....AC....a
1BD....#.....#
AC.....AC.....
#.....#.......
.............0

예제 출력 D

plaintext
26

예제 입력 E

plaintext
48 7
1FD....
BC....a
#.....#
#cd....
.BD....
AC.....
#.....#
f#...##
1##.###
##....a
#.....#
#......
..#b#..
...#...
#.....#
.......
#.....#
f#...##
1##.###
##....a
#.....#
#......
..#b#..
...#...
#.....#
.......
.BD....
AC.....
#.....#
f#...##
1##.###
##....a
#.....#
#......
..#b#..
...#...
#.....#
.......
#.....#
f#...##
1##.###
##....a
#.....#
#......
..#b#..
...#...
#.....#
......0

예제 출력 E

plaintext
67

세로 크기 N과 가로 크기 M (1 ≤ N, M ≤ 50) 소문자 알파벳 열쇠 / 대문자 알파벳 문 1은 적어도 한 개 이상 0은 한 개 탈출이 불가능하다면 -1 출력 BFS로 접근할 생각

입력 확인 코드

cpp
#include<iostream>

using namespace std;

constexpr auto MAX = 51;;

int N, M; // 세로 N 가로 M
int startX, startY; // 처음 시작위치를 저장하기 위함 
char map[MAX][MAX];




void Initalization()
{
	cin >> N >> M;

	//전부 이동이 불가능한 벽으로 변경
	for (int i = 0; i <= N+1; i++)
	{
		for (int j = 0; j <= M+1; j++)
		{
			map[i][j] = '#';
		}
	}

	for (int i = 1; i <= N; i++)
	{
		for (int j = 1; j <= M ; j++)
		{
			cin >> map[i][j];
			if (map[i][j] == '0')
			{
				startY = i;
				startX = j;
			}
		}
	}
}


int main()
{
	Initalization();

	 cout << startX << " " << startY << "  "<< map[0][1];
	 cout << "\n--------------------------------------------\n";
	 
	 for (int i = 0; i <= N + 1; i++)
	 {
	 	for (int j = 0; j <= M + 1; j++)
	 	{
	 		cout << map[i][j];
	 	}
	 	cout << "\n";
	 }

	return 0;
}

중간 코드

cpp
#include <iostream>
#include <queue>
#include <algorithm>

using namespace std;

constexpr auto MAX = 51;;


int N, M; // 세로 N 가로 M
int startX, startY; // 처음 시작위치를 저장하기 위함 
char map[MAX][MAX];

// 하 상 우 좌
int dx[] = { 0, 0, 1, -1 };
int dy[] = { 1, -1, 0, 0 };

typedef struct myStruct {
	int x, y;
	bool checkKey[26] = { false };
	int cnt = 0;
	bool checkMap[MAX][MAX] = { false };
};

myStruct tempStruct;

queue<myStruct> myQueue;



void Initalization()
{
	cin >> N >> M;
	myStruct tempStruct;

	//전부 이동이 불가능한 벽으로 변경
	for (int i = 0; i < MAX; i++)
	{
		for (int j = 0; j < MAX; j++)
		{
			map[i][j] = '#';
		}
	}

	for (int i = 1; i <= N; i++)
	{
		for (int j = 1; j <= M; j++)
		{
			cin >> map[i][j];
			if (map[i][j] == '0')
			{
				startY = i;
				startX = j;
				cout << "(startX, startY) : " << startX << " /" << startY << "\n";
			}
		}
	}
}

int BFS()
{

	map[startY][startX] = '.';

	myStruct mystruct;
	mystruct.checkMap[startY][startX] = true;
	mystruct.x = startX;
	mystruct.y = startY;
	myQueue.push(mystruct);
	cout << myQueue.size() << "\n";
	while (!myQueue.empty())
	{
		tempStruct = myQueue.front();
		tempStruct.cnt = myQueue.front().cnt + 1;
		myQueue.pop();
		for (int k = 0; k < 4; k++)
		{
			cout << "\n-----------------움직임 전---------------------\n";

			for (int i = 0; i <= N + 1; i++)
			{
				for (int j = 0; j <= M + 1; j++)
				{
					cout << map[i][j];
				}
				cout << "\n";
			}


			switch (k)
			{
				case 0:
					cout << "\n하\n";
					break;
				case 1:
					cout << "\n상\n";
					break;
				case 2:
					cout << "\n우\n";
					break;
				case 3:
					cout << "\n좌\n";
					break;
			}

			mystruct = tempStruct;
			cout << "mystruct.cnt : "<< mystruct.cnt << "\n";
			cout << "이동전의 위치 : mystruct.x : " << mystruct.x << " mystruct.y : " << mystruct.y << "\n";
			// 상하 좌우로 검색
			mystruct.x = tempStruct.x + dx[k];
			mystruct.y = tempStruct.y + dy[k];
	
			// 맵의 범위를 넘어가거나 // 벽을 만나거나 //  
			if (mystruct.x <= 0 || mystruct.x > M || mystruct.y <= 0 || mystruct.y > N || map[mystruct.y][mystruct.x] == '#' || mystruct.checkMap[mystruct.y][mystruct.x] == true)
			{
				cout << "checkMap[mystruct.y][mystruct.x] : " << mystruct.checkMap[mystruct.y][mystruct.x] << "\n";
				cout << "이동한 후의 위치 : map[mystruct.y][mystruct.x] : " << map[mystruct.y][mystruct.x] << "\n";
				cout << "이동한 후의 위치 :  " << "mystruct.x : " << mystruct.x << " mystruct.y : " << mystruct.y << "\n";
				cout << "맵의 범위를 넘어가거나 // 벽을 만나거나 " << "\n\n";
				continue;
			}
			else if( 65 <= (int)map[mystruct.y][mystruct.x] && (int)map[mystruct.y][mystruct.x] <= 90 )
			{
				cout << "map[mystruct.y][mystruct.x] : " << map[mystruct.y][mystruct.x] << "\n";
				cout << "(int)map[mystruct.y][mystruct.x]] : " << (int)map[mystruct.y][mystruct.x] << "\n";
				cout << "(int)map[mystruct.y][mystruct.x]-65] : " << (int)map[mystruct.y][mystruct.x] - 65 << "\n";
				// 키가 없으면 넘어간다.
				if (mystruct.checkKey[(int)map[mystruct.y][mystruct.x]-65] == false)
				{
					cout << "// 키가 없으면 넘어간다." << "\n\n";
					cout << "이동한 후의 위치 : map[mystruct.y][mystruct.x] : " << map[mystruct.y][mystruct.x] << "\n";
					cout << "이동한 후의 위치 :  " << "mystruct.x : " << mystruct.x << " mystruct.y : " << mystruct.y << "\n";
					continue;
				}
				else
				{
					cout << "// 키가 있으면 빈칸으로 만든다." << "\n\n";
					cout << "이동한 후의 위치 : map[mystruct.y][mystruct.x] : " << map[mystruct.y][mystruct.x] << "\n";
					cout << "이동한 후의 위치 :  " << "mystruct.x : " << mystruct.x << " mystruct.y : " << mystruct.y << "\n";
					map[mystruct.y][mystruct.x] = '.';
					mystruct.checkMap[mystruct.y][mystruct.x] = true;
				}

			}
			else if (97 <= (int)map[mystruct.y][mystruct.x] && (int)map[mystruct.y][mystruct.x] <= 122)
			{
				cout << "/키를 줍고 열쇠 위치를 빈칸으로 만들고 전체 방문 체크 초기화" << "\n\n";
				cout << "이동한 후의 위치 : map[mystruct.y][mystruct.x] : " << map[mystruct.y][mystruct.x] << "\n";
				cout << "이동한 후의 위치 :  " << "mystruct.x : " << mystruct.x << " mystruct.y : " << mystruct.y << "\n";
				mystruct.checkKey[(int)map[mystruct.y][mystruct.x] - 97] = true;
				map[mystruct.y][mystruct.x] = '.';

				for (int i = 1; i <= N; i++)
				{
					for (int j = 1; j <= M; j++)
					{
						cout << "초기화";
						mystruct.checkMap[i][j] = false;
					}
				}

			}
			else if (map[mystruct.y][mystruct.x] == '1')
			{
				// 미로를 탈출합니다.
				return mystruct.cnt;
			}
			else if (map[mystruct.y][mystruct.x] == '.'|| map[mystruct.y][mystruct.x] == '@')
			{
				cout << "빈 공간에 도착" << "\n\n";
				cout << "이동한 후의 위치 : map[mystruct.y][mystruct.x] : " << map[mystruct.y][mystruct.x] << "\n";
				cout << "이동한 후의 위치 :  " << "mystruct.x : " << mystruct.x << " mystruct.y : " << mystruct.y << "\n";
				mystruct.checkMap[mystruct.y][mystruct.x] = true;
				map[mystruct.y][mystruct.x] = '@';
			}
			cout << "\n-----------------움직임 후---------------------\n";

			for (int i = 0; i <= N + 1; i++)
			{
				for (int j = 0; j <= M + 1; j++)
				{
					cout << map[i][j];
				}
				cout << "\n";
			}
			cout << "map[mystruct.y][mystruct.x] : "<< map[mystruct.y][mystruct.x] << "큐에 저장\n";
			myQueue.push(mystruct);
			cout << myQueue.size()<<"\n";
		}
	}

	// 출구를 찾지 못했을 경우 -1 리턴
	return -1;
	
	
	// cout << "A : " << (int)'A' << " a : " << (int)'a' << "\n";
	// cout << "B : " << (int)'B' << " b : " << (int)'b' << "\n";
	//A : 65 Z : 90
	//  a : 97
	//B: 66 b : 98


}

int main()
{
	 cout << ". : " << (int)'.' << " . : " << (int)'.' << "\n";
	// cout << "a : " << (int)'a' << " z : " << (int)'z' << "\n";
	// cout << "B : " << (int)'B' << " b : " << (int)'b' << "\n";
	Initalization();

	cout << "\n--------------------------------------------\n";

	for (int i = 0; i <= N + 1; i++)
	{
		for (int j = 0; j <= M + 1; j++)
		{
			cout << map[i][j];
		}
		cout << "\n";
	}


	cout << "\n\n 답 : " << BFS() << "\n\n\n";

	cout << startX << " " << startY << "  ";
	 cout << "\n--------------------------------------------\n";
	 
	 for (int i = 0; i <= N + 1; i++)
	 {
	 	for (int j = 0; j <= M + 1; j++)
	 	{
	 		cout << map[i][j];
	 	}
	 	cout << "\n";
	 }

	return 0;
}

BFS로 탐색하다 열쇠를 주우면 방문을 초기화 메모리 초과로 고통 받는다. 방문을 체크하는데 및 키를 체크하는데 문제가 있는 듯 하다

메모리 초과 코드 1

cpp
#include <iostream>
#include <queue>
#include <algorithm>

using namespace std;

constexpr auto MAX = 51;


int N, M; // 세로 N 가로 M
int startX, startY; // 처음 시작 위치를 저장하기 위함 
char map[MAX][MAX];
bool checkMap[MAX][MAX][1 << 6] = {false};
// 하 상 우 좌
int dx[] = { 0, 0, 1, -1 };
int dy[] = { 1, -1, 0, 0 };

typedef struct myStruct {
	int x, y;
	int checkKey = 0;
	int cnt = 0;
};

myStruct tempStruct;

queue<myStruct> myQueue;



void Initalization()
{
	cin >> N >> M;
	myStruct tempStruct;

	//전부 이동이 불가능한 벽으로 변경
	for (int i = 0; i < MAX; i++)
	{
		for (int j = 0; j < MAX; j++)
		{
			map[i][j] = '#';
		}
	}

	for (int i = 1; i <= N; i++)
	{
		for (int j = 1; j <= M; j++)
		{
			cin >> map[i][j];
			if (map[i][j] == '0')
			{
				startY = i;
				startX = j;
			}
		}
	}
}

int BFS()
{

	map[startY][startX] = '.';
	myStruct mystruct;
	
	mystruct.x = startX;
	mystruct.y = startY;
	myQueue.push(mystruct);
	while (!myQueue.empty())
	{
		tempStruct = myQueue.front();
		tempStruct.cnt = myQueue.front().cnt + 1;
		myQueue.pop();
		for (int k = 0; k < 4; k++)
		{
			mystruct = tempStruct;
			mystruct.x = tempStruct.x + dx[k];
			mystruct.y = tempStruct.y + dy[k];

			if (mystruct.x <= 0 || mystruct.x > M || mystruct.y <= 0 || mystruct.y > N || map[mystruct.y][mystruct.x] == '#')// )|| checkMap[mystruct.y][mystruct.x][mystruct.checkKey] == true)
			{
				continue;
			}
			else if( 65 <= (int)map[mystruct.y][mystruct.x] && (int)map[mystruct.y][mystruct.x] <= 90 )
			{


				if ((mystruct.checkKey & 1 << ((int)map[mystruct.y][mystruct.x] - 64)) <= 0 )
				{
					continue;
				}
				else
				{
					
				}
			}
			else if (97 <= (int)map[mystruct.y][mystruct.x] && (int)map[mystruct.y][mystruct.x] <= 122)
			{

				mystruct.checkKey = mystruct.checkKey | 1 << ((int)map[mystruct.y][mystruct.x] - 96);
			}
			else if (map[mystruct.y][mystruct.x] == '1')
			{
				return mystruct.cnt;
			}
			else if (map[mystruct.y][mystruct.x] == '.')
			{
				
			}
			checkMap[mystruct.y][mystruct.x][mystruct.checkKey] = true;
			myQueue.push(mystruct);
		}
	}

	return -1;
}

int main()
{
	Initalization();
	cout << BFS();
	return 0;
}

메모리 초과 코드 2

cpp
#include <iostream>
#include <queue>
#include <algorithm>

using namespace std;

constexpr auto MAX = 51;


int N, M; // 세로 N 가로 M
int startX, startY; // 처음 시작 위치를 저장하기 위함 
char map[MAX][MAX];
bool checkMap[MAX][MAX][1 << 6] = { false };
// 하 상 우 좌
int dx[] = { 0, 0, 1, -1 };
int dy[] = { 1, -1, 0, 0 };



typedef struct myStruct {
	int x, y;
	int checkKey = 0;
	int cnt = 0;
};

myStruct tempStruct;
queue<myStruct> myQueue;



void Initalization()
{
	cin >> N >> M;
	for (int i = 0; i < MAX; i++)
	{
		for (int j = 0; j < MAX; j++)
		{
			map[i][j] = '#';
		}
	}

	for (int i = 1; i <= N; i++)
	{
		for (int j = 1; j <= M; j++)
		{
			cin >> map[i][j];
			if (map[i][j] == '0')
			{
				startY = i;
				startX = j;
			}
		}
	}
}

int BFS()
{
	myStruct mystruct;
	map[startY][startX] = '.';
	mystruct.x = startX;
	mystruct.y = startY;
	myQueue.push(mystruct);
	while (!myQueue.empty())
	{
		tempStruct = myQueue.front();
		tempStruct.cnt = myQueue.front().cnt + 1;
		myQueue.pop();
		for (int k = 0; k < 4; k++)
		{
			mystruct = tempStruct;
			mystruct.x = tempStruct.x + dx[k];
			mystruct.y = tempStruct.y + dy[k];

			if (mystruct.x <= 0 || mystruct.x > M || mystruct.y <= 0 || mystruct.y > N || map[mystruct.y][mystruct.x] == '#' || checkMap[mystruct.y][mystruct.x][tempStruct.checkKey] == true)
			{
				continue;
			}
			else if (65 <= (int)map[mystruct.y][mystruct.x] && (int)map[mystruct.y][mystruct.x] <= 90)
			{

				if ((mystruct.checkKey & 1 << ((int)map[mystruct.y][mystruct.x] - 'A')) > 0)
				{
					map[mystruct.y][mystruct.x] = '.';
				}
				else
				{
					checkMap[mystruct.y][mystruct.x][mystruct.checkKey] = true;
					continue;
				}
			}
			else if (97 <= (int)map[mystruct.y][mystruct.x] && (int)map[mystruct.y][mystruct.x] <= 122)
			{
				mystruct.checkKey = mystruct.checkKey | 1 << ((int)map[mystruct.y][mystruct.x] - 'a');
			}
			else if (map[mystruct.y][mystruct.x] == '1')
			{
				return mystruct.cnt;
			}
			else if (map[mystruct.y][mystruct.x] == '.')
			{
				checkMap[mystruct.y][mystruct.x][mystruct.checkKey] = true;
			}
			myQueue.push(mystruct);
		}
	}

	return -1;
}

int main()
{
	Initalization();
	cout << BFS();
	return 0;
}

계속 메모리 초과로 인하여 변경한 내용 -> 키의 유무를 비트 마스킹으로 해결 -> 방문 마킹의 위치 변경

통과된 코드

plaintext
#include <iostream>
#include <queue>
#include <algorithm>

using namespace std;
constexpr auto MAX = 51;

int N, M; // 세로 N 가로 M
int startX, startY; // 처음 시작위치를 저장 
char map[MAX][MAX]; // 맵을 저장
bool checkMap[MAX][MAX][1 << 6] = { false }; // 비트 마스킹
// 하 상 우 좌
int dx[] = { 0, 0, 1, -1 };
int dy[] = { 1, -1, 0, 0 };

typedef struct myStruct {
	int x, y;
	int checkKey = 0;
	int cnt = 0;
};

myStruct tempStruct;
queue<myStruct> myQueue;



void Initalization()
{
	cin >> N >> M;
	for (int i = 0; i < MAX; i++)
	{
		for (int j = 0; j < MAX; j++)
		{
			map[i][j] = '#';
		}
	}

	for (int i = 1; i <= N; i++)
	{
		for (int j = 1; j <= M; j++)
		{
			cin >> map[i][j];
			if (map[i][j] == '0')
			{
				startY = i;
				startX = j;
			}
		}
	}
}

int BFS()
{
	myStruct mystruct;
	map[startY][startX] = '.';
	mystruct.x = startX;
	mystruct.y = startY;
	myQueue.push(mystruct);
	while (!myQueue.empty())
	{
		tempStruct = myQueue.front();
		tempStruct.cnt = myQueue.front().cnt + 1;
		myQueue.pop();
		for (int k = 0; k < 4; k++)
		{
			mystruct = tempStruct;
			mystruct.x = tempStruct.x + dx[k];
			mystruct.y = tempStruct.y + dy[k];

			// 범위를 벗어나거나 벽이 있을 경우에 넘어간다.
			if (mystruct.x <= 0 || mystruct.x > M || mystruct.y <= 0 || mystruct.y > N || map[mystruct.y][mystruct.x] == '#' 
				|| checkMap[mystruct.y][mystruct.x][tempStruct.checkKey] == true)
			{
				continue;
			}
			else if (65 <= (int)map[mystruct.y][mystruct.x] && (int)map[mystruct.y][mystruct.x] <= 90)
			{ // 문의 위치일 경우

				// 열쇠를 가지고 있다면
				if ((mystruct.checkKey & 1 << ((int)map[mystruct.y][mystruct.x] - 'A')) > 0)
				{
					checkMap[mystruct.y][mystruct.x][mystruct.checkKey] = true;
				}
				else
				{
					//열쇠가 없다면 넘어간다.
					continue;
				}
			}
			else if (97 <= (int)map[mystruct.y][mystruct.x] && (int)map[mystruct.y][mystruct.x] <= 122)
			{
				// 열쇠의 위치일 경우 비트 마스킹
				mystruct.checkKey = mystruct.checkKey | 1 << ((int)map[mystruct.y][mystruct.x] - 'a');
			}
			else if (map[mystruct.y][mystruct.x] == '.')
			{
				// 빈곳일경우 
			}
			else if (map[mystruct.y][mystruct.x] == '1')
			{
				// 도착지점
				return mystruct.cnt;
			}

			checkMap[mystruct.y][mystruct.x][mystruct.checkKey] = true;
			myQueue.push(mystruct);
		}
	}

	return -1;
}

int main()
{
	Initalization();
	cout << BFS();
	return 0;
}

메모리 초과로 고통받은 문제