분류: bfs /
문제
문제 설명
여러 섬으로 이루어진 나라가 있다. 이 나라의 대통령은 섬을 잇는 다리를 만들겠다는 공약으로 인기몰이를 해 당선될 수 있었다. 하지만 막상 대통령에 취임하자, 다리를 놓는다는 것이 아깝다는 생각을 하게 되었다. 그래서 그는, 생색내는 식으로 한 섬과 다른 섬을 잇는 다리 하나만을 만들기로 하였고, 그 또한 다리를 가장 짧게 하여 돈을 아끼려 하였다.
이 나라는 N×N크기의 이차원 평면상에 존재한다. 이 나라는 여러 섬으로 이루어져 있으며, 섬이란 동서남북으로 육지가 붙어있는 덩어리를 말한다. 다음은 세 개의 섬으로 이루어진 나라의 지도이다.
위의 그림에서 색이 있는 부분이 육지이고, 색이 없는 부분이 바다이다. 이 바다에 가장 짧은 다리를 놓아 두 대륙을 연결하고자 한다. 가장 짧은 다리란, 다리가 격자에서 차지하는 칸의 수가 가장 작은 다리를 말한다. 다음 그림에서 두 대륙을 연결하는 다리를 볼 수 있다.
물론 위의 방법 외에도 다리를 놓는 방법이 여러 가지 있으나, 위의 경우가 놓는 다리의 길이가 3으로 가장 짧다(물론 길이가 3인 다른 다리를 놓을 수 있는 방법도 몇 가지 있다).
지도가 주어질 때, 가장 짧은 다리 하나를 놓아 두 대륙을 연결하는 방법을 찾으시오.
입력
첫 줄에는 지도의 크기 N(100이하의 자연수)가 주어진다. 그 다음 N줄에는 N개의 숫자가 빈칸을 사이에 두고 주어지며, 0은 바다, 1은 육지를 나타낸다. 항상 두 개 이상의 섬이 있는 데이터만 입력으로 주어진다.
출력
첫째 줄에 가장 짧은 다리의 길이를 출력한다.
풀이
#include <iostream>
#include <queue>
using namespace std;
struct xy {int x,y;};
int N;
int board[100][100];
int vis[100][100];
int dist[100][100];
queue<xy> land[5001];
int dx[] = {1, 0, -1, 0};
int dy[] = {0, 1, 0, -1};
int findLand() {
int landCnt = 0;
for(int i=0; i<N; i++) {
for(int j=0; j<N; j++) {
if(board[i][j] == 0 || vis[i][j]) continue;
queue<xy> Q;
Q.push({i, j});
vis[i][j] = ++landCnt;
while(!Q.empty()) {
xy cur = Q.front(); Q.pop();
bool isBorder = false;
for(int d=4; d--;) {
int nx = cur.x + dx[d], ny = cur.y + dy[d];
if(nx<0 || nx>=N || ny<0 || ny>=N) continue;
if(vis[nx][ny]) continue;
if(board[nx][ny] == 0) {
isBorder = true;
continue;
}
vis[nx][ny] = landCnt;
Q.push({nx, ny});
}
if(isBorder) land[landCnt].push(cur);
}
}
}
return landCnt;
}
int makeBridge(int landNum) {
auto &Q = land[landNum];
while(!Q.empty()) {
xy cur = Q.front(); Q.pop();
for(int d=4; d--;) {
int nx = cur.x + dx[d], ny = cur.y + dy[d];
if(nx<0 || nx>=N || ny<0 || ny>=N) continue;
if(vis[nx][ny] == landNum) continue;
if(board[nx][ny] == 0) {
vis[nx][ny] = landNum;
dist[nx][ny] = dist[cur.x][cur.y] + 1;
Q.push({nx, ny});
}
else return dist[cur.x][cur.y];
}
}
return 200;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> N;
for(int i=0; i<N; i++)
for(int j=0; j<N; j++)
cin >> board[i][j];
int landCnt = findLand();
int ans = 200;
for(int i=1; i<=landCnt; i++)
ans = min(ans, makeBridge(i));
cout << ans;
}
- `board`를 돌며 섬을 찾고 섬의 수(`landCnt`)를 센다. (`findLand`)
- 각 섬에 대해 다음을 행한다. (`landNum`은 각 섬의 번호다.)
- 각 섬에 대해 방문 처리한다.(`vis = landNum`)
- 각 섬의 해안선을 큐에 저장한다. (`land[landNum].push`)
- 각 섬(`i`)에서 가장 가까운 섬까지의 거리를 측정한다. (`makeBridge(i)`)
- 해안선에서 시작해 바다로 거리를 측정해 나가며 다른 섬을 찾으면 멈춘다.
- 큐에는 해안선 좌표가 담겨있다. (`&Q = land[landNum]`)
- 섬은 이미 `landNum`으로 방문처리가 되어있어 자기 자신은 무시한다.
- 바다이면 거리를 확장해 나간다.(`board[nx][ny] == 0`)
- 새로운 섬을 발견하면 거리 측정을 종료한다.(`else`)
- 다른 조건을 확인하지 않고 else만 해도 되는 이유는 다음과 같다.
- `vis`에서 이미 해당 섬은 걸러진다.
- 바다가 아니고 해당 섬이 아니라면 다른 섬이다.
A 섬의 `landNum`으로 B 섬의 `vis`가 채워질 일이 없다.
섬의 최대 수는 5000개다. 따라서 `landNum`은 1 ~ 5000의 값을 가질 수 있어 `land`의 길이는 5001이다.
`ans`의 최댓값은 197이다.
방문값을 다르게하여 `vis`를 매번 초기화하지 않는 방법은 [백준 -9466] 텀 프로젝트를 풀며 익혔었다. 텀 프로젝트 문제에서 한번 생각해 봤던 방법이라 바로 떠올릴 수 있었다.
해안선을 저장해 메모리와 시간을 절약하는 방법은 [백준 - 2573] 빙산문제에서 생각해낸 방법이었다. 컴포넌트 사이의 거리를 구하는 것을 보고 바로 떠올릴 수 있었다.
bfs는 보통 가중치가 같은 그래프에서 노드와 노드사이의 최단거리를 구할 때 사용된다. 컴포넌트 사이의 거리를 구할 때 해안선을 저장하는 방법을 자주 애용할 것 같다.
'Problem Solving > BOJ' 카테고리의 다른 글
[백준 - 13549] 숨바꼭질 3 - C++ (0) | 2023.10.16 |
---|---|
[백준 - 1600] 말이 되고픈 원숭이 - C++ (0) | 2023.10.16 |
[백준 - 9466] 텀 프로젝트 - C++ (0) | 2023.10.13 |
[백준 - 2573] 빙산 - C++ (1) | 2023.10.13 |
[백준 - 2206] 벽 부수고 이동하기 - C++ (0) | 2023.10.11 |