포스트

백준 14502. 연구소

알고리즘 중급 문제 중 611 - BFS(연습)

연구소

1. 간단 설명


image

N * M 인 연구소 맵

0은 빈칸, 1은 벽, 2는 바이러스

바이러스는 상하좌우 인접한 빈 칸으로 퍼져나간다

격벽을 3개 세워서 바이러스를 차단해서, 얻을 수 있는 안전 영역의 최대 크기를 구하는 문제

2. 예제


1
2
3
4
5
6
7
2 0 0 0 1 1 0
0 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 0 0
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0
1
2
3
4
5
6
7
2 1 0 0 1 1 0
1 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 1 0
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

다음과 같이 벽을 세우면

1
2
3
4
5
6
7
2 1 0 0 1 1 2
1 0 1 0 1 2 2
0 1 1 0 1 2 2
0 1 0 0 0 1 2
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

다음과 같이 바이러스가 퍼지게 되고, 안전 영역은 27만큼 얻을 수 있다

3. 알고리즘


가장 먼저 떠오르는 방법은

  1. N*M 에서 0인 부분에 차례대로 3부분을 선택하여 맵을 생성한뒤

  2. 이를 BFS를 돌려 안전구역의 크기를 계산하는 방법인데,

이게 시간 내에 가능할지 궁금했다

N, M의 크기가 최대값인 8인 경우,

1
2
3
4
5
6
7
8
0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2

다음과 같은 예시가 있다면,

63C3 = (63 * 62 * 61) / (3 * 2 * 1) = 41664 경우의 수의 BFS를 구해야한다

BFS를 구하는데 걸리는 시간은 O(V+E)이므로,

V = 64, E = 4V = 4 * 64 = 256

각 BFS 시간 복잡도는 O(320)정도이므로,

총 시간 복잡도는 320 * 41664 = 2,666,496 정도 걸리는 것을 알 수 있다.

충분히 2초 내로 해결 가능할듯 하다

알고리즘

1
2
3
4
5
6
7
1. 맵을 입력받는다
2. 3중 for문을 돌면서 차례대로 3곳을 선택하여 1로 수정한다
	- 차례로 세곳을 선택하는 방법으로 DFS를 사용했다
3. 수정한 맵을 토대로 BFS를 돌리고, 안전구역의 크기를 계산한다
4. 구한 안전구역의 크기를 기록된 최대값과 비교하여 갱신한다
5. 맵을 원상복구한다
6. 모든 경우를 체크할 때 까지 이를 반복한다

필요 변수

  • int N, M (3 ≤ N, M ≤ 8)
  • 0은 빈칸, 1은 벽, 2는 바이러스를 기록할 int map[8][8]
  • 바이러스의 시작 좌표를 기록해둘 벡터 vector<pair<int,int>> virus
  • BFS탐색을 위한 int BFS()
    • 4방향 탐색 int direct[4][2]
    • BFS 탐색용 큐 queue<pair<int, int>> qu
    • 방문체크용 맵은 별도로 사용하지 않고, 맵에 그대로 기록하는 방법을 사용함
  • 최대 안전구역의 크기 int max_safehouse_size

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include <iostream>
#include <vector>
#include <queue>
#include <memory.h>

#define MAX 8

using namespace std;

int N, M;
int map[MAX][MAX];
int backup[MAX][MAX];
int max_safehouse_size = 0;
int direct[4][2] = { 0,1,1,0,0,-1,-1,0 };

struct node {
	int y;
	int x;
};

vector<node> virus;
vector<node> wall;

void Input() {
	freopen_s(new FILE*, "input.txt", "r", stdin);
	cin >> N >> M;
	for (int y = 0; y < N; y++) {
		for (int x = 0; x < M; x++) {
			cin >> map[y][x];

			// 바이러스인 경우 기록해둠
			if (map[y][x] == 2) {
				virus.push_back({ y, x });
			}
		}
	}
}

int BFS() {
	queue<node> qu;
	// 모든 바이러스 시작점을 큐에 넣음
	for (node e : virus) {
		qu.push(e);
	}
	
	while (!qu.empty()) {
		node now = qu.front();
		qu.pop();

		for (int t = 0; t < 4; t++) {
			int dy = direct[t][0] + now.y;
			int dx = direct[t][1] + now.x;

			if (dy < 0 || dy >= N || dx < 0 || dx >= M) continue; // 맵 밖으로 벗어난 경우
			if (map[dy][dx] != 0) continue; // 빈칸이 아닌경우 (이미 바이러스가 있거나, 벽)

			map[dy][dx] = 2;
			qu.push({ dy, dx });
		}
	}

	int size = 0;
	for (int yy = 0; yy < N; yy++) {
		for (int xx = 0; xx < M; xx++) {
			if (map[yy][xx] == 0) {
				size++;
			}
		}
	}
	
	return size;
}

void DFS(int level) {
	// 3개 고른 경우
	if (level == 3) {
		memcpy(backup, map, sizeof(map)); // BFS 수행중 원본이 손상되므로, 백업해둠
		max_safehouse_size = max(max_safehouse_size, BFS());
		memcpy(map, backup, sizeof(map)); // 복원
		return;
	}

	for (int yy = 0; yy < N; yy++) {
		for (int xx = 0; xx < M; xx++) {
			if (map[yy][xx] == 0) {
				wall.push_back({ yy, xx });
				map[yy][xx] = 1;

				DFS(level + 1);

				wall.pop_back();
				map[yy][xx] = 0;
			}
		}
	}
}

int main() {
	Input();

	// 빈칸중 3칸을 고른다 
	DFS(0);

	cout << max_safehouse_size;

	BFS();

	return 0;
}

2차원 배열을 백업하고, 복원하는데 사용하였다

또한, DFS에서 직접 탐색하며 빈칸을 찾는 방법을 사용하였는데

빈칸을 미리 기록해두고, 이 중 3개를 선택하는 방법을 사용하면 더 효율적으로 구할 수 있을 것 같다

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.