BOJ 1944

복제로봇

문제출처

문제 해석

  • N*N의 정사각형 미로와 M개의 흩어진 열쇠의 위치, 시작위치가 주어졌을 때, 모든 열쇠를 얻기위해 이동해야하는 최소 거리의 합을 구하는 문제입니다.
  • 로봇은 상하좌우로 움직이며, 열쇠가 있는 위치에 도달했을 때 열쇠를 찾은 것으로 합니다.
  • 출발하는 위치와 열쇠위치에 도달하면 로봇이 복제할 수 있습니다.
  • 하나의 칸에 여러 개의 로봇이 위치할 수 있으며, 로봇이 한번 지나간 자리라도 다른 로봇 또는 자기 자신이 다시 지나갈 수 있습니다.
  • 로봇이 움직이는 횟수의 합은 분열된 로봇 각각이 움직인 횟수의 총합을 의미합니다.

설계(X)

  • 처음에 구현한 방법은 우선순위 큐를 이용해서 S또는 K가 있는 위치에 도달한 경우, 해당 열쇠 또는 시작위치 표시를 없애고 0부터 다시 시작하는 정보를 큐에 넣어서 진행하도록 하였습니다. 그러나 접근법이 너무나 틀렸습니다.
  • 해당 블로그를 통해 아이디어를 얻을 수 있었습니다.
    1. 첫번째로, 정점(S와 K)간의 최소거리인 간선을 bfs탐색을 이용하여 구합니다.
    2. 두번째로, 구해진 간선을 최소스패닝 트리로 최소 간선비용의 합을 구하면 정답이 됩니다.
  • 여기서 2차원 배열에 있는 정점을 간선과 대응시키기 위하여, 별도의 2차원 배열에 각 정점의 번호를 저장해 놓습니다.
  • 입력 정보를 받을 때, S와 K의 좌표를 벡터에 저장한 후, 모든 정점에 대해서 bfs알고리즘을 통해 간선을 잇게 됩니다.
  • 간선을 이동거리의 오름차순으로 정렬 후, 크루스칼 알고리즘으로 최소 이동거리의 합을 구합니다.
  • 시간복잡도는 모든 간선(M)에 대해서 bfs(N^2)을 진행하기 때문에 O(M*N^2)이 되어 시간안에 구현이 가능합니다.

구현(30)

코드
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>

using namespace std;
#define MAXN 50

char map[MAXN][MAXN];
int parent[252];
bool visited[MAXN][MAXN];
int N,M;
int num[MAXN][MAXN];
struct node{
    int u;
    int v;
    int w;
    bool operator < (const node& a) const{
        return w < a.w;
    };
};
vector<node> adj;
vector<pair<int,int> > coord;
int dx[] = {0,0,1,-1};
int dy[] = {1,-1,0,0};
int find(int x){
    if (x == parent[x]) return x;
    return parent[x] = find(parent[x]);
}
bool merge(int u, int v){
    u = find(u);
    v = find(v);
    if (u == v) return false;
    parent[u] = v;
    return true;
}
bool bfs(int start){
    queue<pair<int,int> > q;
    memset(visited,false,sizeof(visited));
    bool flag = false;
    q.push(coord[start]);
    visited[coord[start].first][coord[start].second] = true;
    int cnt = 0;
    while (!q.empty()){
        int q_size = q.size(); 
        while (q_size--){
            int x = q.front().first;
            int y = q.front().second;
            q.pop();
            for (int k =0 ;k<4 ;k++){
                int nx = x+ dx[k];
                int ny = y + dy[k];
                if (nx < 0 || ny < 0 || nx>=N || ny>=N) continue;
                if (map[nx][ny] == '1' || visited[nx][ny]) continue;
                visited[nx][ny] = true;
                if (map[nx][ny] == 'K'){
                    adj.push_back({start,num[nx][ny],cnt+1});
                    flag = true;
                }
                else{
                    q.push({nx,ny});
                }
            }
        }
        cnt++;
    }
    return flag;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> N >> M;
    int idx = 0;
    for (int i =0 ;i<N;i++){
        for (int j=0;j<N;j++){
            cin >> map[i][j];
            if (map[i][j] == 'S' || map[i][j] == 'K'){
                if (map[i][j] == 'S') map[i][j] = 'K';
                num[i][j] = idx++;
                coord.push_back({i,j});
            }
        }
    }
    for (int i = 0 ;i < coord.size(); i++){
        if (!bfs(i)){
            cout << "-1\n";
            return 0;
        }
    }
    sort(adj.begin(),adj.end());
    for (int i = 0 ;i < idx; i++){
        parent[i] = i;
    }
    int ans = 0;
    for (int i = 0 ; i < adj.size(); i++){
        if (merge(adj[i].u,adj[i].v)){
            ans += adj[i].w;
        }
    }
    cout << ans <<"\n";
    return 0;
}
디버깅
제출결과
  • 0ms

마무리

  • 2차원 배열의 정점을 간선화하기 위해 쓰이는 bfs와 최소스패닝트리(MST)를 이용한 문제였습니다. 개인적으로 구현연습하기에 손색이 없을 정도로 잘 만들어진 문제라고 생각합니다. 여러가지 알짜배기 구현요소들이 들어있으니 구현해보는 것을 추천합니다.