[백준/C++]2146번_ 다리 만들기🌉

이수진·2022년 2월 23일
0

문제는 다음과 같습니다.

무려 이 문제 푸는데 이틀이나 걸렸다..

악 진짜 의지의 k-한국인 ..
(낼 수 있는 에러 다 내본듯;;)

먼저 이 문제를 푸는 과정은 두 단계로 나눠지고,
첫번째 단계에서는 DFS가 수행되고,
두번째 단계에서는 BFS가 수행됩니다.

말그대로 DFS와 BFS를 모두 공부할 수 있는 좋은 문제라는 것﹒﹒

과정1 : DFS를 수행하여, 각 섬들을 구별시킨다.

예를 들면,
1 1 0 0 1
1 1 0 0 1
0 0 0 0 0
1 1 0 1 1
1 1 1 0 1
의 분포를 가진 나라가 있다고 하면

여기서 DFS를 수행하여 섬을 구분지으면 다음과 같이 됩니다.

1 1 0 0 2
1 1 0 0 2
0 0 0 0 0
3 3 0 4 4
3 3 3 0 4

저는 숫자로 각 섬을 구분짓는 동시에 섬의 개수를 측정하였습니다.

dfs를 수행하여 각 섬을 구분짓는 코드는 다음과 같습니다.

  int color = 1;
  for(int i=1; i<=n; i++){
    for(int j=1; j<=n; j++){
      if(ch[i][j]==0 && a[i][j]){
        ch[i][j] = color;
        DFS(i, j, color);
        color++;
      }
    }
  } // 섬마다 구획나누기
  
  // 실제 dfs가 수행되는 dfs 함수
  
  void DFS(int t1, int t2, int col){
  if(t1+1<=n && a[t1+1][t2]==1 && ch[t1+1][t2]==0){
    ch[t1+1][t2]=col; DFS(t1+1, t2, col);
  }
  if(t2+1<=n && a[t1][t2+1]==1 && ch[t1][t2+1]==0){
    ch[t1][t2+1]=col; DFS(t1, t2+1, col);
  }
  if(t1-1>=1 && a[t1-1][t2]==1 && ch[t1-1][t2]==0){
    ch[t1-1][t2]=col; DFS(t1-1, t2, col);
  }
  if(t2-1>=1 && a[t1][t2-1]==1 && ch[t1][t2-1]==0){
    ch[t1][t2-1]=col; DFS(t1, t2-1, col);
  }
}

과정2 : 섬과 섬 사이의 최단거리 구하기🔥

처음에 생각한 건 이 섬들을 모두 큐에 넣은 후에 한 번의 bfs로 이 섬들 간의 거리를 구하려고 했습니다.
하지만 계속해서 틀렸었고, 틀린 이유는 계속해서 예외가 발생하였습니다.
📌결국 생각한 건, 각각의 섬들에서 bfs를 수행하는 것이었습니다.📌
제한시간을 2초까지 준 것도 이 때문이 아닐까 싶네요

각 섬들에서 bfs를 수행하여 최단 거리를 벡터에 저장하여, 벡터에서 가장 작은 값을 반환하도록 하여 가장 짧은 다리를 구하였습니다.

각각의 섬들에서 bfs를 수행하는 코드는 다음과 같습니다.

  // bfs 시작
  vector<int> res;
  
  for(int i=1; i<color; i++){
    int d[101][101]={0, };
    queue<pair<pair<int, int>, int>> q; int col = i;
    
    for(int i=1; i<=n; i++){
      for(int j=1; j<=n; j++){
        if(ch[i][j]==col){
          d[i][j]=1; q.push({{i, j}, 0});
        }
      }
    }

    while(!q.empty()){
      int x = q.front().first.first; int y = q.front().first.second; int dis = q.front().second; q.pop();

      if(ch[x][y]!=0 && ch[x][y]!=col){ res.push_back(dis-1); break; }

      if(x+1<=n && d[x+1][y]==0) { d[x+1][y]=1; q.push({{x+1, y}, dis+1}); }
      if(y+1<=n && d[x][y+1]==0) { d[x][y+1]=1; q.push({{x, y+1}, dis+1}); }
      if(x-1>=1 && d[x-1][y]==0) { d[x-1][y]=1; q.push({{x-1, y}, dis+1}); }
      if(y-1>=1 && d[x][y-1]==0) { d[x][y-1]=1; q.push({{x, y-1}, dis+1}); }
    }
    
  }
  
  sort(res.begin(), res.end());
  cout<<res[0]<<endl;

전체 코드는 다음과 같습니다.

#include <bits/stdc++.h>
using namespace std;

int a[101][101]={0, }; int ch[101][101]={0, }; int n;

void DFS(int t1, int t2, int col){
  if(t1+1<=n && a[t1+1][t2]==1 && ch[t1+1][t2]==0){
    ch[t1+1][t2]=col; DFS(t1+1, t2, col);
  }
  if(t2+1<=n && a[t1][t2+1]==1 && ch[t1][t2+1]==0){
    ch[t1][t2+1]=col; DFS(t1, t2+1, col);
  }
  if(t1-1>=1 && a[t1-1][t2]==1 && ch[t1-1][t2]==0){
    ch[t1-1][t2]=col; DFS(t1-1, t2, col);
  }
  if(t2-1>=1 && a[t1][t2-1]==1 && ch[t1][t2-1]==0){
    ch[t1][t2-1]=col; DFS(t1, t2-1, col);
  }
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(nullptr);

  cin>>n;
  for(int i=1; i<=n; i++){
    for(int j=1; j<=n; j++){
      cin>>a[i][j];
    }
  }

  int color = 1;
  for(int i=1; i<=n; i++){
    for(int j=1; j<=n; j++){
      if(ch[i][j]==0 && a[i][j]){
        ch[i][j] = color;
        DFS(i, j, color);
        color++;
      }
    }
  } // 섬마다 구획나누기

  // bfs 시작
  vector<int> res;

  for(int i=1; i<color; i++){
    int d[101][101]={0, };
    queue<pair<pair<int, int>, int>> q; int col = i;
    
    for(int i=1; i<=n; i++){
      for(int j=1; j<=n; j++){
        if(ch[i][j]==col){
          d[i][j]=1; q.push({{i, j}, 0});
        }
      }
    }

    while(!q.empty()){
      int x = q.front().first.first; int y = q.front().first.second; int dis = q.front().second; q.pop();

      if(ch[x][y]!=0 && ch[x][y]!=col){ res.push_back(dis-1); break; }

      if(x+1<=n && d[x+1][y]==0) { d[x+1][y]=1; q.push({{x+1, y}, dis+1}); }
      if(y+1<=n && d[x][y+1]==0) { d[x][y+1]=1; q.push({{x, y+1}, dis+1}); }
      if(x-1>=1 && d[x-1][y]==0) { d[x-1][y]=1; q.push({{x-1, y}, dis+1}); }
      if(y-1>=1 && d[x][y-1]==0) { d[x][y-1]=1; q.push({{x, y-1}, dis+1}); }
    }
    
  }
  
  sort(res.begin(), res.end());
  cout<<res[0]<<endl;
  return 0;
}

추가)

마지막에서 섬에서 섬까지 다리를 놓을 때,
저는 모든 섬에서 다른 섬까지의 bfs를 구해서 이를 해결했는데,
한 번의 bfs 수행으로만 이를 구할 수 있더라구요!

(난 했었는데 .. 실패했는데)
아무튼 엄청난 시간복잡도의 개선이 이루어질 수 있는 풀이라
공부하고 넘어가면 너무 좋을 것 같아 가져왔어요

풀이의 핵심은 다음과 같습니다.

  1. 모든 섬을 queue에 넣습니다.
  2. 섬에서 거리를 넓혀갈 때, 거리 뿐만 아니라 어디에서 뻗어나온 섬인지를 기록합니다 -> (저는 ch배열을 재사용했습니다.)

즉 이렇게 1번만 bfs를 수행합니다.

그리고 다시 이차원 배열의 모든 점들에 대해서 다음을 검토합니다.
한 점에서 그에 이웃한 네 점을 살펴보는 데,
❗️출신 섬이 다를 경우 -> 다리를 놓을 수 있다❗️ 는 점을 이용합니다.

또한 이 때의 다리가 격자에서 차지하는 칸은 이 이웃한 두 칸의 거리의 합과 같습니다.

이를 이용한 개선된 전체 코드는 다음과 같습니다.
(마지막 bfs 수행되는 부분이 개선되었습니다.)

#include <bits/stdc++.h>
using namespace std;

int a[101][101]={0, }; int ch[101][101]={0, }; int n;

void DFS(int t1, int t2, int col){
 if(t1+1<=n && a[t1+1][t2]==1 && ch[t1+1][t2]==0){
   ch[t1+1][t2]=col; DFS(t1+1, t2, col);
 }
 if(t2+1<=n && a[t1][t2+1]==1 && ch[t1][t2+1]==0){
   ch[t1][t2+1]=col; DFS(t1, t2+1, col);
 }
 if(t1-1>=1 && a[t1-1][t2]==1 && ch[t1-1][t2]==0){
   ch[t1-1][t2]=col; DFS(t1-1, t2, col);
 }
 if(t2-1>=1 && a[t1][t2-1]==1 && ch[t1][t2-1]==0){
   ch[t1][t2-1]=col; DFS(t1, t2-1, col);
 }
}

int main() {
 ios_base::sync_with_stdio(false);
 cin.tie(nullptr);

 cin>>n;
 for(int i=1; i<=n; i++){
   for(int j=1; j<=n; j++){
     cin>>a[i][j];
   }
 }

 int color = 1;
 for(int i=1; i<=n; i++){
   for(int j=1; j<=n; j++){
     if(ch[i][j]==0 && a[i][j]){
       ch[i][j] = color;
       DFS(i, j, color);
       color++;
     }
   }
 } // 섬마다 구획나누기

 // bfs 시작
 int d[101][101]; memset(d, -1, sizeof(d)); // d 배열: 거리 배열이자 체크 배열
 queue<pair<int, int>> q;
   
 for(int i=1; i<=n; i++){
   for(int j=1; j<=n; j++){
     if(ch[i][j]!=0){ // 모든 섬들에 대해서
       d[i][j]=0; q.push({i, j});
     }
   }
 }

 while(!q.empty()){
   int x = q.front().first; int y = q.front().second; q.pop();
     
   if(x+1<=n && d[x+1][y]==-1) { ch[x+1][y]=ch[x][y]; d[x+1][y]=d[x][y]+1; q.push({x+1, y}); }
   if(y+1<=n && d[x][y+1]==-1) { ch[x][y+1]=ch[x][y]; d[x][y+1]=d[x][y]+1; q.push({x, y+1}); }
   if(x-1>=1 && d[x-1][y]==-1) { ch[x-1][y]=ch[x][y]; d[x-1][y]=d[x][y]+1; q.push({x-1, y}); }
   if(y-1>=1 && d[x][y-1]==-1) { ch[x][y-1]=ch[x][y]; d[x][y-1]=d[x][y]+1; q.push({x, y-1}); }
 } // bfs 끝

 int ans = 100000; int dx[4] = {-1, 0, 1, 0}; int dy[4] = {0, 1, 0, -1};
 for(int i=1; i<=n; i++){
   for(int j=1; j<=n; j++){
     int dis = d[i][j]; int col = ch[i][j];

     // 상, 하, 좌, 우 를 검토 시 서로 다른 섬이면 다리를 놓을 수 있음
     for(int k=0; k<4; k++){
       int x = i+dx[k]; int y = j+dy[k];
       if(x>=1 && x<=n && y>=1 && y<=n && ch[x][y]!=col){
         int tmp_dis = dis + d[x][y];
         if(tmp_dis < ans) ans=tmp_dis;
       }
     }
   }
 }
 
 cout<<ans<<endl;
 return 0;
}

그리고 한 점에서 그에 이웃한 네 점을 살펴볼 때,
int dx[4] = {-1, 0, 1, 0};
int dy[4] = { 0, 1, 0, -1};
을 이용해 한 번의 반복문을 돌리면 매우 쉽게 이웃한 점들을 살펴볼 수 있습니다.

각각을 if문으로 돌리는 것 보다 이렇게 깔끔하게 한 번의 for문으로 수행하는 것이 더 좋은 것 같습니다.😊

이렇게 시간복잡도가 개선된 것을 확인할 수 있습니다🥳🥳

profile
꾸준히, 열심히, 그리고 잘하자

0개의 댓글