벽 부수고 이동하기 4
2024. 8. 17. 17:47ㆍBOJ/BFS,DFS
문제
문제 설명
- 배열에서 1의 경우에는 이동할 수 없는 벽이 있는 곳이고 0이 있는 경우는 이동할 수 있는 곳을 의미한다.
- 한 칸의 벽을 부수고 이동할 수 있는 부분을 구해준다.
문제 해설
- 처음에는 벽을 하나씩 부수면서 이동할 수 있는 공간을 모두 탐색하는 방식으로 문제를 해결하려고 하였지만 이럴 경우에는 시간초과가 발생할 가능성이 높기 때문에 다른 방법으로 접근해야 했다.
- 모든 칸을 확인하면서 방문하지 않고 이동할 수 있는 부분부터 탐색을 시작한다.
- 그 칸부터 이동할 수 있는 부분이 몇 군데 있는지를 확인 해준다.
- 이때 칸을 방문할 때 연결된 칸들은 같은 숫자로 체크해 주고 연결되지 않은 부분은 다른 숫자로 체크해 준다.
- 그 칸을 방문하면서 체크했던 숫자를 인덱스로 하여서 이동할 수 있는 칸의 개수를 저장해 준다.
- 그런 다음 모든 칸을 확인하면서 이번에는 이동할 수 없는 부분을 방문하여서 상하좌우를 탐색해 준다. 이전에 방문 체크 해주었던 숫자를 통해서 이동할 수 있는 부분의 크기를 확인해 준다.


설명을 이해하기 힘들기 때문에 코드를 살펴보면서 이해하는 것이 제일 좋다.
최종 코드
#include <iostream>
#include <queue>
#include <set>
#include <vector>
using namespace std;
int n, m;
int a[1004][1004];
int visited[1004][1004];
int gs[1000004];
const int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
int main() {
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> n >> m;
for (int i = 0; i < n; i++) {
string s;
cin >> s;
for (int j = 0; j < m; j++) a[i][j] = s[j] - '0';
}
int cnt = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (a[i][j] == 0 && visited[i][j] == 0) {
queue<pair<int, int>> q;
q.push({i, j});
visited[i][j] = cnt;
int size = 0;
while (!q.empty()) {
int x = q.front().first, y = q.front().second;
q.pop();
size+=1;
for (int d = 0; d < 4; d++) {
int xx = x + dx[d], yy = y + dy[d];
if (xx >= 0 && xx < n && yy >= 0 && yy < m && a[xx][yy] == 0 && !visited[xx][yy]) {
visited[xx][yy] = cnt;
q.push({xx, yy});
}
}
}
gs[cnt] = size;
cnt++;
}
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (a[i][j] == 1) {
set<int> s;
int mov = 1;
for (int d = 0; d < 4; d++) {
int xx = i + dx[d], yy = j + dy[d];
if (xx >= 0 && xx < n && yy >= 0 && yy < m && !a[xx][yy]) {
int cnt = visited[xx][yy];
if (s.find(cnt) == s.end()) {
mov += gs[cnt];
s.insert(cnt);
}
}
}
cout << mov % 10;
}
else cout << 0;
}
cout << '\n';
}
}
코드에서 살펴보아야 하는 부분은 두 가지 부분이다.
- 모든 칸을 돌아보면서 방문처리와 연결된 부분의 크기 저장해 주기
int cnt = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (a[i][j] == 0 && visited[i][j] == 0) {
queue<pair<int, int>> q;
q.push({i, j});
visited[i][j] = cnt;
int size = 0;
while (!q.empty()) {
int x = q.front().first, y = q.front().second;
q.pop();
size+=1;
for (int d = 0; d < 4; d++) {
int xx = x + dx[d], yy = y + dy[d];
if (xx >= 0 && xx < n && yy >= 0 && yy < m && a[xx][yy] == 0 && !visited[xx][yy]) {
visited[xx][yy] = cnt;
q.push({xx, yy});
}
}
}
gs[cnt] = size;
cnt++;
}
}
}
기본적인 BFS탐색이지만 방문처리와 이동할 수 있는 칸의 수를 저장해 주는 부분에서 차이가 있다.
- cnt: 서로 연결된 부분들은 cnt로 방문 처리를 해주어야 한다. 그리고 BFS탐색이 끝났다면 cnt를 +1해 주고 또 이동할 수 있는 칸을 찾으면서 연결되지 않은 부분은 서로 다른 숫자로 채워주어야 한다.
- visited 배열: cnt로 방문처리를 해준다.
- size: 연결된 칸들의 크기를 카운트해준다.
- gs 배열: gs [cnt]=size, 즉 방문 처리를 해준 cnt 값에 대한 크기를 gs배열에 저장해 준다.
예시를 들어보자면 다음과 같다.
4 5
11001
00111
01010
10101
- 이동할 수 있는 칸인 (0,2)부터 탐색을 시작한다.
- (0,2)와 (0,3)은 visited배열에 1로 체크가 되고 gs배열에는 gs [1]=2의 형태로 저장된다.
- cnt를 +1해 주면서 다음 이동할 수 있는 부분을 탐색해 준다.
- 벽이 있는 칸, 즉 이동할 수 없는 칸을 탐색해 주기
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (a[i][j] == 1) {
set<int> s;
int mov = 1;
for (int d = 0; d < 4; d++) {
int xx = i + dx[d], yy = j + dy[d];
if (xx >= 0 && xx < n && yy >= 0 && yy < m && !a[xx][yy]) {
int cnt = visited[xx][yy];
if (s.find(cnt) == s.end()) {
mov += gs[cnt];
s.insert(cnt);
}
}
}
cout << mov % 10;
}
else cout << 0;
}
cout << '\n';
- s: 상하좌우를 탐색하면서 visited배열에 체크된 숫자들을 저장해 주는 역할을 한다. s는 체크된 숫자들이 반복되는지 판단하는 데 사용된다
- mov: gs배열에 저장되어 있는 이동할 수 있는 크기를 모두 더해준다.
이러한 과정을 통해서 mov를 출력해 주면서 이동가능한 칸의 크기를 저장해 준다.
'BOJ > BFS,DFS' 카테고리의 다른 글
| 섬의 개수(BOJ4943) (1) | 2024.11.16 |
|---|---|
| 게리맨더링(BOJ 17471) (0) | 2024.11.15 |
| BFS 스페셜 저지 (BOJ 16940) (1) | 2024.08.15 |
| 다리 만들기(BOJ 2146번) (0) | 2024.06.20 |
| 배수 찾기(BOJ 4994번) (2) | 2024.05.04 |

