백준 1261. 알고스팟
알고리즘 기초 문제 중 610 - BFS
1. 간단 설명
(1,1)에서 (N,M)으로 이동하는데 벽을 부수지 않으면 이동할 수 없는 경우가 존재함
벽을 최소로 부시면서 최단경로로 이동할 때,
벽을 최소 몇개 부숴야하는지 구하는 문제
2. 예시
예제 1)
1
2
3
4
5
6
3 3
011
111
110
3
최소 3개는 부셔야 도달 가능
예제 2)
1
2
3
4
5
4 2
0001
1000
0
하나도 부시지 않고 도달 가능
예제 3)
1
2
3
4
5
6
7
8
5 5
01000
01010
01010
01010
00010
0
하나도 부시지 않고 도달 가능
3. 알고리즘
벽 부수고 이동하기2 와 유사하게 visited 배열을 3차원으로 사용한다
필요 변수
- 가로 세로 크기
int N, M
(1 ≤ N, M ≤ 100) - 맵 정보
int map[101][101]
- 방향 정보
int direct[4] = { 오른쪽, 아래, 왼쪽, 위 }
- 맵 방문 여부
int visited[y좌표][x좌표][부신 벽의 개수]
- 벽을 최대로 부실 수 있는 경우의 수는 100 100 에서 모든 맵이 벽으로 막힌 경우 197개 부셔야함
- 부신 벽의 최소 개수
int answer
알고리즘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. N, M 입력받기
2. BFS 초기 세팅
- 큐에 시작지점 넣기
- 해당 노드 방문처리
3. BFS 시작
3-1. 큐에서 하나 꺼내기
3-2. 해당 노드가 도착지인지 체크
3-3. 다음 방문할 노드 4방향 선택
3-3-1. 맵 경계 벗어났는지 체크
3-3-2. 방문한적 있는지 체크
3-3-3. 벽이 아니라면
- 기존 BFS처럼 처리한다 (같은 층을 이동)
3-3-4. 벽이라면
- 이 벽을 부순적이 있는지 체크,
- 벽을 부수고, 큐에 이를 등록함
주의사항
- 벽 부시기2와는 다른 문제다
벽을 k개 부신다고 visited[101][101][101] 이렇게 설정하면
벽을 몇개 부시던간 최단거리만 계산하기 때문에
1
2
3
4
5
01000
01010
01010
01010
00010
와 같은 경우 최단거리를 벽 하나 부시고 도달하는 8로 계산해버린다
이 문제에서는 벽을 최소로 부시는 회수를 구하라고 했으므로 이는 명백히 다르다
또한, N, M까지 도달하는 경로가 여러개 있을 수 있으므로, N,M에 도달했을 때 기존의 값과 비교 후 갱신해준다
문제 입력에서 N과 M이 바뀌어서 제공됨
4. 소스코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <iostream>
#include <queue>
using namespace std;
int N, M;
int map[101][101];
int visited[101][101][200];
int direct[4][2] = {0,1,1,0,0,-1,-1,0};
int answer = 21e8;
struct node {
int y;
int x;
int level;
};
void Input() {
// freopen_s(new FILE*, "input.txt", "r", stdin);
cin >> M >> N;
for (int y = 0; y < N; y++) {
string input; cin >> input;
for (int x = 0; x < input.length(); x++) {
map[y][x] = input[x] - '0';
}
}
}
void BFS() {
queue<node> qu;
qu.push({ 0, 0, 0 });
visited[0][0][0] = 1;
while (!qu.empty()) {
node now = qu.front();
qu.pop();
if (now.y == N - 1 && now.x == M - 1) {
answer = min(answer, now.level);
}
for (int t = 0; t < 4; t++) {
int dy = direct[t][0] + now.y;
int dx = direct[t][1] + now.x;
if (dy < 0 || dx < 0 || dy >= N || dx >= M) continue; // 경계처리
if (visited[dy][dx][now.level] != 0) continue; // 현재 레벨에서 해당 노드 방문여부 체크
// 벽인경우
if (map[dy][dx] == 1) {
if (visited[dy][dx][now.level + 1] != 0) continue; // 이미 한번 부신 경우
visited[dy][dx][now.level + 1] = visited[now.y][now.x][now.level] + 1;
qu.push({ dy, dx, now.level + 1 });
}
// 벽이 아닌경우
else {
visited[dy][dx][now.level] = visited[now.y][now.x][now.level] + 1;;
qu.push({ dy, dx, now.level });
}
}
}
return;
}
int main() {
Input();
BFS();
cout << answer;
return 0;
}
배운점
BFS 함수에서
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
for (int t = 0; t < 4; t++) {
int dy = direct[t][0] + now.y;
int dx = direct[t][1] + now.x;
if (dy < 0 || dx < 0 || dy >= N || dx >= M) continue; // 경계처리
if (visited[dy][dx][now.level] != 0) continue; // 현재 레벨에서 해당 노드 방문여부 체크
// 벽인경우
if (map[dy][dx] == 1) {
if (visited[dy][dx][now.level + 1] != 0) continue; // 이미 한번 부신 경우
visited[dy][dx][now.level + 1] = visited[now.y][now.x][now.level] + 1;
qu.push({ dy, dx, now.level + 1 });
}
// 벽이 아닌경우
else {
visited[dy][dx][now.level] = visited[now.y][now.x][now.level] + 1;;
qu.push({ dy, dx, now.level });
}
}
BFS 문제에서 관련 탐색 문제는 이 부분을 어떻게 구현하느냐에 따라 다양하게 갈리는 것 같다
각 노드를 탐색하는 방식과, 방문 체크 방법을 통해 알고리즘의 성능과 정확도에 크게 영향을 준다
1
2
3
4
5
6
7
8
9
10
for (int i = 0; i < 4; ++i) {
int nextX = current.x + dx[i];
int nextY = current.y + dy[i];
if (nextX < 0 || nextY < 0 || nextX >= N || nextY >= M) continue;
if (visited[nextX][nextY]) continue;
visited[nextX][nextY] = true;
queue.push({nextX, nextY});
}
이처럼 방문체크하고, 방문처리 후 큐에 넣는게 기본형이라면
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for (int i = 0; i < 4; ++i) {
int nextX = current.x + dx[i];
int nextY = current.y + dy[i];
if (nextX < 0 || nextY < 0 || nextX >= N || nextY >= M) continue;
if (visited[nextX][nextY]) continue;
if (map[nextX][nextY] == 0) {
deq.push_front({nextX, nextY, current.cost});
} else {
deq.push_back({nextX, nextY, current.cost + 1});
}
visited[nextX][nextY] = true;
}
덱을 이용한 0-1 BFS 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
for (int i = 0; i < 4; ++i) {
int nextX = current.x + dx[i];
int nextY = current.y + dy[i];
int nextLevel = current.level;
if (nextX < 0 || nextY < 0 || nextX >= N || nextY >= M) continue;
if (visited[nextX][nextY][nextLevel]) continue;
if (map[nextX][nextY] == 1) {
if (!visited[nextX][nextY][nextLevel + 1]) {
visited[nextX][nextY][nextLevel + 1] = true;
queue.push({nextX, nextY, nextLevel + 1});
}
} else {
visited[nextX][nextY][nextLevel] = true;
queue.push({nextX, nextY, nextLevel});
}
}
이 문제나 벽부시기 2 와 같이 추가 정보를 고려하여 방문 체크를 해야하는 경우
1
2
3
4
for (auto start : startPoints) {
queue.push(start);
visited[start.x][start.y] = true;
}
또는 여러 출발지점을 동시에 시뮬레이션 해야하는 경우도 존재한다
최대한 다양한 문제들을 다루어볼 것!
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.