[백준] 1944 복제 로봇 (C++)

문제

원문 링크 : https://www.acmicpc.net/problem/1944

세준이는 어느 날 획기적인 로봇을 한 개 개발하였다. 그 로봇은 복제 장치를 이용하면 자기 자신을 똑같은 로봇으로 원하는 개수만큼 복제시킬 수 있다. 세준이는 어느 날 이 로봇을 테스트하기 위하여 어떤 미로에 이 로봇을 풀어 놓았다. 이 로봇의 임무는 미로에 흩어진 열쇠들을 모두 찾는 것이다. 그리고 열쇠가 있는 곳들과 로봇이 출발하는 위치에 로봇이 복제할 수 있는 장치를 장착해 두었다.

N*N의 정사각형 미로와 M개의 흩어진 열쇠의 위치, 그리고 이 로봇의 시작 위치가 주어져 있을 때, 모든 열쇠를 찾으면서 로봇이 움직이는 횟수의 합을 최소로 하는 프로그램을 작성하여라. 로봇은 상하좌우 네 방향으로 움직이며, 로봇이 열쇠가 있는 위치에 도달했을 때 열쇠를 찾은 것으로 한다. (복제된 로봇이어도 상관없다) 하나의 칸에 동시에 여러 개의 로봇이 위치할 수 있으며, 로봇이 한 번 지나간 자리라도 다른 로봇 또는 자기 자신이 다시 지나갈 수 있다. 복제에는 시간이 들지 않으며, 로봇이 움직이는 횟수의 합은 분열된 로봇 각각이 움직인 횟수의 총 합을 말한다. 복제된 로봇이 열쇠를 모두 찾은 후 같은 위치로 모일 필요는 없다.

풀이 및 구현

문제를 요약해보면 시작 위치 S에서부터 열쇠가 있는 위치 K를 모두 들리는 최단 경로를 찾는 문제이다.

S와 K를 모두 정점으로 놓았을 때 각 정점에서 로봇이 분열할 수 있고, 하나의 로봇이라도 정점에 도착하면 되기 때문에 모든 정점을 최소 가중치로 연결하는 MST를 구하면 된다.

그래서 먼저 BFS를 이용해 그래프를 만들고, 그래프의 MST를 구하면 된다.

정점 정보는 map<pair<int, int>, int>>에서 x, y좌표를 넣으면 정점의 인덱스를 반환하게 했고, S = 0, K = 1, 2, 3, … 순으로 번호를 매겼다.

각 정점마다 BFS를 돌면서 다른 정점과 연결 상태를 확인했다.
정점과 연결이 되어있으면 그래프에 {도착노드, 거리} 형태로 저장을 한다.

이때 BFS를 모든 정점에 대해 돌기 때문에 단방향으로 저장을 했다.

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
// 그래프를 만든다.
void BFS(int sx, int sy) {
int n = v[{sx, sy}];
vector<vector<int>> visited(N, vector<int>(N));
queue<tuple<int, int, int>> q;
q.push({0, sx, sy});

while (!q.empty()) {
auto [cur_w, cur_x, cur_y] = q.front();
q.pop();

if (cur_x < 0 || cur_x >= N || cur_y < 0 || cur_y >= N) continue;
if (m[cur_x][cur_y] == '1') continue;
if (visited[cur_x][cur_y]) continue;

visited[cur_x][cur_y] = 1;

// 정점에 도착했을 때
if (m[cur_x][cur_y] == 'S' || m[cur_x][cur_y] == 'K') {
if (cur_w) {
auto m = v[{cur_x, cur_y}];
adj[n].push_back({m, cur_w});
}
}

for (int i = 0; i < 4; i++) {
int nxt_x = cur_x + dx[i];
int nxt_y = cur_y + dy[i];
int nxt_w = cur_w + 1;
q.push({nxt_w, nxt_x, nxt_y});
}
}
}

그래프가 완성이 되면 Prim 알고리즘을 통해 MST를 만들었다.

모든 열쇠를 찾는게 불가능 할 경우 -1을 반환해야 하므로 거리가 INF인 가중치가 있을 때는 -1을 반환해주고 그게 아니라면 가중치의 합을 모두 계산해 합을 반환해주었다.

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
// 만들어진 MST로 최소 이동 거리를 계산한다.
int Prim() {
vector<int> d(M + 1, INF);
priority_queue<pair<int, int>> pq;
pq.push({0, 0});
int cnt = 0;

while (!pq.empty()) {
auto [cur_w, cur_n] = pq.top();
cur_w = -cur_w;
pq.pop();

if (d[cur_n] != INF) continue;
d[cur_n] = cur_w;
if (++cnt == M + 1) break;

for (auto& [nxt_n, nxt_w] : adj[cur_n]) {
pq.push({-nxt_w, nxt_n});
}
}

int sum = 0;
for (auto i : d) {

// 모든 열쇠를 찾을 수 없기 때문에 -1을 반환한다.
if (i == INF) return -1;
sum += i;
}
return sum;
}

코드

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
#include <iostream>
#include <vector>
#include <queue>
#include <tuple>
#include <map>
#define INF (int) 1e9
using namespace std;

int dx[4] = {0, 1, 0, -1};
int dy[4] = {-1, 0, 1, 0};

int N, M;
vector<string> m;
map<pair<int, int>, int> v;
vector<vector<pair<int, int>>> adj;

void BFS(int sx, int sy) {
int n = v[{sx, sy}];
vector<vector<int>> visited(N, vector<int>(N));
queue<tuple<int, int, int>> q;
q.push({0, sx, sy});
while (!q.empty()) {
auto [cur_w, cur_x, cur_y] = q.front();
q.pop();

if (cur_x < 0 || cur_x >= N || cur_y < 0 || cur_y >= N) continue;
if (m[cur_x][cur_y] == '1') continue;
if (visited[cur_x][cur_y]) continue;

visited[cur_x][cur_y] = 1;
if (m[cur_x][cur_y] == 'S' || m[cur_x][cur_y] == 'K') {
if (cur_w) {
auto m = v[{cur_x, cur_y}];
adj[n].push_back({m, cur_w});
}
}

for (int i = 0; i < 4; i++) {
int nxt_x = cur_x + dx[i];
int nxt_y = cur_y + dy[i];
int nxt_w = cur_w + 1;
q.push({nxt_w, nxt_x, nxt_y});
}
}
}

int Prim() {
vector<int> d(M + 1, INF);
priority_queue<pair<int, int>> pq;
pq.push({0, 0});
int cnt = 0;

while (!pq.empty()) {
auto [cur_w, cur_n] = pq.top();
cur_w = -cur_w;
pq.pop();

if (d[cur_n] != INF) continue;
d[cur_n] = cur_w;
if (++cnt == M + 1) break;

for (auto& [nxt_n, nxt_w] : adj[cur_n]) {
pq.push({-nxt_w, nxt_n});
}
}

int sum = 0;
for (auto i : d) {
if (i == INF) return -1;
sum += i;
}
return sum;
}

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

cin >> N >> M;
m.assign(N, string());
int vc = 1;

for (int i = 0; i < N; i++) {
cin >> m[i];

for (int j = 0; j < N; j++) {
if (m[i][j] == 'S') v.insert({{i, j}, 0});
else if (m[i][j] == 'K') v.insert({{i, j}, vc++});
}
}
adj.assign(M + 1, vector<pair<int, int>>());

for (auto i : v) {
BFS(i.first.first, i.first.second);
}

cout << Prim();
}

후기


미로의 크기 $N$의 범위가 $(4 \leq N \leq 50)$ 열쇠의 개수 $M$의 범위가 $(1 \leq M \leq 250)$이기 때문에 각 정점에 대해 모두 BFS를 돌더라도 시간이 여유로워 나이브하게 구현한 문제였다.

주어진 문제를 그래프를 변환하는 것이 번거롭긴 하지만 변환만 된다면 쉽게 풀 수 있는 문제라 생각한다.