2206 벽부수고이동하기

초기 코드

#include <iostream>
#include <queue>
#include <cstdio>
#include <string.h>
using namespace std; 

int n, m; 
int board[1001][1001]; 
int dy[4] = { 0, 0, 1 ,-1 };
int dx[4] = { 1, -1, 0, 0 };
int dist[1001][1001]; 
const int INF = 987654321;

bool isOut(pair<int, int> p) {
	int y = p.first, x = p.second;
	return (y <= 0 || y > n || x <= 0 || x > m); 
}

int bfs() {
	memset(dist, -1, sizeof(dist)); 

	queue<pair<int, int>> q;
	q.push(make_pair(1, 1));
	dist[1][1] = 1;
	while (!q.empty()) {
		pair<int, int> here = q.front(); q.pop();
		for (int direction = 0; direction < 4; direction++) {
			pair<int, int> there = make_pair(here.first + dy[direction], here.second + dx[direction]);
			if (isOut(there)) continue; 
			if (board[there.first][there.second] == 1) continue; //벽이면 이동할 수 없다 .
			if (dist[there.first][there.second] == -1) {
				q.push(there); 
				dist[there.first][there.second] = dist[here.first][here.second] + 1; 
			}
		}
	}
	if (dist[n][m] == -1) return INF; 
	return dist[n][m]; 
}

int main() {
	cin >> n >> m; 
	vector<string> input(n + 1);
	for (int i = 1; i <= n; i++)
		for (int j = 0; j < m; j++)
			scanf("%1d", &board[i][j]); 

	int ret = INF; 
	int cand = bfs(); // 벽을 부수지 않는 경우
	if (ret > cand) ret = cand; 
	for (int y = 1; y <= n; y++)
		for (int x = 1; x <= m; x++) {
			if (board[y][x] == 1) {
				board[y][x] = 0; // 벽 부수기
				cand = bfs(); 
				if (ret > cand) ret = cand; 
				board[y][x] = 1; // 원상복구
			}
		}
	if (ret == INF) cout << -1 << endl;
	else cout << ret << endl;
	return 0; 
}

시간 초과가 나는 코드다. 아무래도 NM = 1000000 에서 하나하나 BFS 를 돌렸는데, 이를 계산하면

100만 * 100만^2 = 100만^3 = 엄청큰 수 .. 가 나오기 때문에 시간초과에 걸렸다.

여기서 NM의 정점을 탐색하다가 벽(1)이 나오면 벽을 부수거나, 부수지 않거나 둘 중 하나만 수행해준다. 참고로, 부수지 않는 다는 것은 지나가지 않는다는 것이니 기저사례 처리는 부수고 지나가는 경우만 해주면 된다. 이때 벽을 부수는 것은 딱 한번만 가능하니, 모든 정점에 벽이 있다고 해도 NM만큼만 탐색해주면 된다.

이를 구현하기 위해서는, y, x 외에도 (y, x)에 있는 벽을 부쉈는지/아닌지 여부를 저장하는 k를 한 개 더 두고, 삼차원 배열로 캐싱한다.

(1) tuple 사용하기

(2) 답이 두 가지 선택지에 의해서 구할 수 있다. 벽을 부수지 않거나, 벽을 부수거나 ! 따라서 dist[n][m][0]에 답이 있을 수도, dist[n][m][1]에 답이 있을 수도 있다. 둘 다 있다면 더 작은 것을 반환해야한다 .

16946 벽부수고이동하기4

#include <iostream>
#include <queue> 
#include <cstring>
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:4996)
using namespace std; 

int n, m; 
int board[1001][1001]; 
int dy[4] = { 0, 0, 1, -1 };
int dx[4] = { 1, -1, 0, 0 };
int dist[1001][1001];

bool isOut(int y, int x) {
	return (y < 0 || y >= n || x < 0 || x >= m); 
}

int bfs(int y, int x) {
	memset(dist, -1, sizeof(dist)); 
	queue<pair<int, int>> q; 
	q.push(make_pair(y, x)); 
	dist[y][x] = 1; 
	int ret = 1; 
	while (!q.empty()) {
		int y = q.front().first, x = q.front().second;
		q.pop();

		for (int direction = 0; direction < 4; direction++) {
			int ny = y + dy[direction], nx = x + dx[direction];
			if (isOut(ny, nx)) continue;
			if (dist[ny][nx] == -1 && board[ny][nx] == 0) {
				ret++; 
				q.push(make_pair(ny, nx)); 
				dist[ny][nx] = dist[y][x] + 1; 
			}
		}
	}
	return ret; 
}

int main() {
	cin >> n >> m; 
	for (int i = 0; i < n; i++)
		for (int j = 0; j < m; j++)
			scanf("%1d", &board[i][j]); 

	// 이동할 수 있는 칸의 개수
	for(int i = 0;i < n; i++){
		for (int j = 0; j < m; j++)
			if (board[i][j] == 1){
				board[i][j] = bfs(i, j); 
				cout << board[i][j]; 
			}
			else if (board[i][j] == 0)
				cout << 0; 
		cout << endl;
	}
	return 0; 
}

이것도 마찬가지로 시간초과가 난다. 이걸 해결하는 나의 아이디어는 아래와 같다.

(1) dist[y][x][k] : (y, x) 칸이 k번 벽에 대한 검사가 이미 완료되었다면 1, 그렇지 않다면 0

(2) BFS 바로 수행. 빈 칸이면 패스하고, 벽이면 주변의 칸들을 탐색한다. 한 벽에 대해서는 고유한 k값이 붙는다. 따라서 그 벽(k = 1이라고 하자)의 주변에 위치한 빈칸들은 dist[y][x][1] = 1; 이 된다. 그리고 이 과정에서 board[ny][nx] = 0이 발견될 때마다 board[y][x]++; 를 수행해준다.

문제점 : 벽의 개수가 최대 1000000개여서 배열에 저장할 수 없다. ⇒ 꼭 배열에 저장해야하나 ? 한번 사용하면 끝인데 함수에서 변수 정의하면 안되나 ? 안되겠네 .. 그럼 배열을 하나 더 생성해서 현재 칸이 어떤 칸을 위해 세어지고 있는지 저장해보자 !

해설