BOJ 2146. 다리 만들기
1. 문제설명
NxN
크기의 맵이 주어진다.- 각 칸은 1 또는 0으로 구성된다.
- 1은 섬, 0은 바다이다.
- 맵은 여러 개의 섬으로 구성되어 있다.
- 여기서 다리를 놓아 두 섬을 연결하고자 한다.
- 단, 가장 짧은 다리를 놓으려고 한다.
- 가장 짧은 다리의 길이를 구하라!
2. 알고리즘 설계
-
예를들어, 섬이 3개가 있다고 가정해보자.
1 1 1 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 1 1 1 0 1 1 0 0 0 0 1 1 0 0 1 1 1 0 0 0 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0
-
각 섬을, 1, 2, 3번으로 구성한다.
1 1 1 0 0 0 0 2 2 2 1 1 1 1 0 0 0 0 2 2 1 0 1 1 0 0 0 0 2 2 0 0 1 1 1 0 0 0 0 2 0 0 0 1 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 0 0 0 0 0 0 0 0 3 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0
- 각 섬에서 바다와 가장 인접한 칸부터 다른 섬을 만날 때까지 찾는다.
- for-loop로 (0,0)부터 시작하면 가장 처음 걸리는 칸은 (0,3)이 된다.
- 다른 섬과 가장 빠른 길(x로 표시)을 찾으면 4칸이다.
1 1 1 x x x x 2 2 2 1 1 1 1 0 0 0 0 2 2 1 0 1 1 0 0 0 0 2 2 0 0 1 1 1 0 0 0 0 2 0 0 0 1 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 0 0 0 0 0 0 0 0 3 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0
- 위와 같은 식으로 모든 경로의 길이를 구한다.
- 가장 짧은 길이를 출력하면 된다.
3. 로직
- 각 섬에 번호를 매기는 방법은 두가지가 있다.
- bfs로 현재 1부터 0이 아닌 부분 전부 번호 매기기
- dfs로 현재 1부터 0이 아닌 부분 전부 번호 매기기
- 섬과 섬 간의 최단 거리는 BFS로 구해주었다.
- 상하좌우 한 칸씩 탐색을 하면서, 움직여 주면 된다.
- 주의할 점
- 섬과 섬 간의 거리를 구할 때, 방문 표시하는 배열과
- 처음 탐색 시작 위치를 방문 표시하는 배열을
- 별도로 두어야 한다.
4. 전체 코드
#include <bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;
typedef struct {
int y, x, dist;
}Pos;
const int dy[] = { -1,1,0,0 };
const int dx[] = { 0,0,-1,1 };
const int SZ = 100, INF = 0x3f3f3f;
int n, ans = INF, division = 1;
pii arr[SZ][SZ];
bool visited[SZ][SZ];
bool tmp[SZ][SZ];
void get_division(int sy, int sx) {
queue<pii> q;
q.push({ sy, sx });
visited[sy][sx] = true;
arr[sy][sx].second = division;
while (!q.empty()) {
pii cur = q.front();
q.pop();
for (int dir = 0; dir < 4; dir++) {
int ny = cur.first + dy[dir];
int nx = cur.second + dx[dir];
if (ny < 0 || nx < 0 || ny >= n || nx >= n)
continue;
if (!visited[ny][nx] && arr[ny][nx].first == 1) {
arr[ny][nx].second = division;
q.push({ ny,nx });
visited[ny][nx] = true;
}
}
}
}
int get_distance(pii src, int cur_division) {
fill_n(&visited[0][0], SZ * SZ, false);
queue<Pos> q;
q.push({ src.first, src.second, 1 });
visited[src.first][src.second] = true;
while (!q.empty()) {
Pos cur = q.front();
q.pop();
if (arr[cur.y][cur.x].first == 1 && arr[cur.y][cur.x].second != cur_division)
return cur.dist - 1;
for (int dir = 0; dir < 4; dir++) {
int ny = cur.y + dy[dir];
int nx = cur.x + dx[dir];
if (ny < 0 || nx < 0 || ny >= n || nx >= n) continue;
if (arr[ny][nx].second != cur_division && !visited[ny][nx]) {
q.push({ ny,nx, cur.dist + 1 });
visited[ny][nx] = true;
}
}
}
return INF;
}
int main(void)
{
ios::sync_with_stdio(false);
cin.tie(NULL);
cin >> n;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) {
cin >> arr[i][j].first;
arr[i][j].second = 0;
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (arr[i][j].first == 1 && !visited[i][j]) {
get_division(i, j);
division++;
}
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (arr[i][j].first == 1) {
for (int dir = 0; dir < 4; dir++) {
int ny = i + dy[dir];
int nx = j + dx[dir];
if (ny < 0 || nx < 0 || ny >= n || nx >= n) continue;
if (arr[ny][nx].first == 0 && !tmp[ny][nx]) {
int dist = get_distance({ ny,nx }, arr[i][j].second);
tmp[ny][nx] = true;
ans = min(ans, dist);
}
}
}
}
}
cout << ans;
return 0;
}
5. 기타
- 처음
get_distance()
에서 정답을 찾았을 때 외엔 반환을 하지 않았다. - 그러나 다음 경우를 간과했다.
- 다른 섬으로 갈 수 없는 경우였다.
1 1 1 1 0 1 1 1 1
- 위 예시는 내 로직에 의하면 (1,1)에서 다른 섬으로 갈 수 있는지 시도한다.
- 그러나 다른 섬에 도착할 수가 없다.
- 그래서 시도하다가 큐가 비어버리고 처음 while-loop를 탈출하게 된다.
- 그러나 return 값이 없다.
- 그래서 이미지와 같은 런타임 에러가 발생했다.
- 이 에러는 발생 원인이 여러가지가 있지만,
- 대표적인 것 중 하나로
main()
을 제외한 반환값이 있는 함수가 반환을 하지 않았을 때 라고 한다.