2146 다리만들기
2146 다리만들기
BFS를 통해서 여러 선택지 중 최단거리를 구하는 문제
https://www.acmicpc.net/problem/2146
1. 문제
간단 설명
- 다음과 같은 섬 3개가 있을 때, 다른 섬을 잇는 다리 하나만을 놓는다
- 근데, 그 다리가 가장 짧은 다리 하나를 놓아 두 대륙을 연결하는 방법 찾기
2. 문제 분석
알고리즘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1. 입력받기
- 섬 라벨링을 가시성을 위해 1부터 시작함, 육지와 번호가 겹치므로, 육지 번호를 -1로 바꿨음
2. BFS를 통해 연결된 땅들은 번호를 붙인다. 연결된 땅들은 같은 라벨을 가진다.
- 바다가 아니고, 방문한 적이 없는 땅이라면 이 땅부터 BFS를 돌려 번호를 붙인다(`Labeling(int y, int x, int label)`)
2-1. 큐에 현재 좌표를 넣고 현재 좌표를 방문처리한다. 또한 맵에도 이 라벨을 붙여 맵을 갱신한다
2-2. 큐가 빌때까지 다음을 반복한다
2-2-1. 큐의 맨 앞을 꺼내 이 노드의 4방향으로 탐색한다.
2-2-2. 경계를 벗어났다면 패스
2-2-3. 바다라면 패스
2-2-4. 이미 방문했던곳이라면 패스
2-2-5. 위 조건들을 모두 통과했다면 방문처리 후, 맵에 라벨을 갱신하고, 큐에 넣는다
- 2번이 종료되면 각 덩어리별로 번호가 부여되어있을것임.
3. 같은 라벨을 가진 섬의 땅 좌표들을 모두 큐에 넣고 최단경로 탐색을 돌린다.
3-1. 방문 배열을 초기화한다
3-2. 큐에 현재 라벨 번호와 같은 땅들을 모두 넣는다
3-3. 이 큐로 BFS를 돌린다. 2번과 알고리즘은 같으므로 생략함
- 경계처리와, 방문했던곳은 패스하는 부분은 같지만,
map[dy][dx] = 0 인 부분을 탐색해야한다(다리를 놓는 알고리즘이므로)
- 또한, 큐에서 현재 꺼낸 노드가
바다도 아니고, 자기와 같은 라벨의 섬이 아닌 경우 칸 개수를 리턴해줘야한다. 이때 -1 해준다.
(이미 섬에 올라와있으므로 다리 - 1개)
4. 최단경로를 출력한다
필요변수
맵 크기 N
2차원 맵 배열 map
, 방문 여부 체크용 2차원 배열 visited
라벨링용 번호 label
, 최소값 체크용 answer
4방향 탐색용 direct[4][2]
배열
y, x좌표와 몇번 이동했는지 기록하기 위한 구조체 node
주의점
- BFS를 두번 돌려야하므로, 방문체크 배열과 맵 갱신을 언제 할 것인지 잘 고려해야한다
- 또한, 경계처리, 맵 조건 확인 등을 잘 고려해야한다.
라벨링할때는 바다인 부분은 패스하지만, 섬을 놓을땐 체크해야한다.(다리를 지어야하므로)
- 방문 배열을 초기화 할 타이밍 고려
3. 소스코드
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
110
111
112
113
114
115
116
117
118
119
120
121
#include <iostream>
#include <queue>
#include <algorithm>
#include <string.h>
using namespace std;
#define MAX 101
int N;
int map[MAX][MAX];
int visited[MAX][MAX];
int label = 1;
int answer = 21e8;
int direct[4][2] = { 1,0,0,1,-1,0,0,-1};
struct node {
int y;
int x;
int level;
};
// BFS를 이용해 같은 덩어리끼리는 같은 번호를 부여하는 함수
void Labeling(int y, int x, int label) {
queue<node> qu;
qu.push({ y,x,0 });
visited[y][x] = 1;
map[y][x] = label;
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 || dx < 0 || dy >= N || dx >= N) continue;
if (map[dy][dx] == 0) continue;
if (visited[dy][dx] == 1) continue;
visited[dy][dx] = 1;
map[dy][dx] = label;
qu.push({ dy,dx,0 });
}
}
}
// label 번호의 섬으로부터 다른 섬까지의 최단거리 계산
int BFS(int label) {
// 먼저 큐에 현재 번호의 섬의 땅을 모두 넣음
memset(visited, 0, sizeof(visited));
queue<node> qu;
for (int y = 0; y < N; y++) {
for (int x = 0; x < N; x++) {
if (map[y][x] == label) {
qu.push({ y,x,0 });
visited[y][x] = 1;
}
}
}
while (!qu.empty()) {
node now = qu.front();
qu.pop();
// 새로운 땅을 밟았다면
if (map[now.y][now.x] != 0 && map[now.y][now.x] != label) {
return now.level - 1; // 이미 섬에 올라와있으므로 다리 - 1개
}
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 >= N) continue;
if (visited[dy][dx] == 1) continue;
visited[dy][dx] = 1;
qu.push({ dy,dx,now.level+1 });
}
}
return 21e8;
}
int main() {
// 1. 입력받기
freopen_s(new FILE*, "input.txt", "r", stdin);
cin >> N;
for (int y = 0; y < N; y++) {
for (int x = 0; x < N; x++) {
int tmp; cin >> tmp;
if (tmp == 1) {
map[y][x] = -1;
}
}
}
// 2. 같은 덩어리끼리 묶어 번호를 붙인다
for (int y = 0; y < N; y++) {
for (int x = 0; x < N; x++) {
if (map[y][x] == -1 && visited[y][x] == 0) {
Labeling(y, x, label);
label++;
}
}
}
// 섬의 개수만큼 최단거리를 계산
for (int i = 1; i <= label; i++) {
answer = min(answer, BFS(i));
}
cout << answer;
return 0;
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.