※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※
이번에 볼 문제는 백준 2887번 문제인 행성 터널이다.
문제는 아래 링크를 확인하자.
2887번: 행성 터널
첫째 줄에 행성의 개수 N이 주어진다. (1 ≤ N ≤ 100,000) 다음 N개 줄에는 각 행성의 x, y, z좌표가 주어진다. 좌표는 -109보다 크거나 같고, 109보다 작거나 같은 정수이다. 한 위치에 행성이 두 개 이
www.acmicpc.net
이 문제에서는, 3차원 좌표에 주어진 N개의 행성들을 모두 이을 수 있는 터널을 만드는 것이 목표이다.
이 문제는 각 N개의 행성들을 그래프의 노드라고 생각하고, 서로 다른 두 행성 사이에 두 행성 사이 거리를 가중치로 갖는 간선이 있는 완전그래프에서 MST(최소신장트리)를 찾는 문제로 생각할 수 있다.
단, 노드의 개수가 최대 10만개까지 가능하므로, 모든 서로 다른 두 행성을 잇는 간선을 생각할 수는 없고(시간 면으로나 메모리 면으로나), MST에 쓰일 수 있는 간선의 후보를 잘 추려내야한다.
다음과 같은 관찰을 하자.
N개의 행성 중, 세 행성 p1, p2, p3가 대하여 x좌표가 오름차순으로 주어져있는 상황을 생각해보자.
이 상황에서, p1, p2 사이의 x좌표의 차가 p1, p2 사이의 거리이고 p2, p3 사이의 x좌표의 차가 p2, p3 사이의 거리이면 p1과 p3를 잇는 간선은 MST에 쓰일 수 없다. 그 증명은 다음과 같다:
p1과 p3을 잇는 간선이 spanning tree에 포함되어있다고 가정하자. 이 spanning tree에서 p1과 p3을 잇는 간선을 지운다면 MST는 두개의 connected component로 분리되게 된다. 이 때, p2는 p1이 포함되어 있는 component와 연결되어있거나 p3이 포함되어 있는 component와 연결되어있다. 각각의 경우 p2와 p3 / p2와 p1을 잇는 간선을 그으면 기존 spanning tree보다 더 적은 총합 가중치를 가진 spanning tree를 만들 수 있다.
위와 같은 증명과 같은 아이디어를 바탕으로, MST에 사용될 수 있는 후보 간선은 x좌표, y좌표, z좌표 순으로 정렬했을 때 서로 인접한 순서에 있는 노드를 잇는 에지들일 것임을 알 수 있다.
이 추려진 (최대 30만개의) 후보간선들을 이용하여 MST를 구하는 Kruskal(크루스칼) 알고리즘을 이용하면 문제를 해결할 수 있다.
아래 구현 코드에서, (예를 들어) x좌표를 기준으로 정렬된 인접한 두 노드를 잇는 간선을 후보간선으로 넣을 때 거리를 실제 거리 대신 두 노드의 x좌표의 차를 이용하고 있는데, 이렇게 구현해도 되는 이유는 실제 거리가 x좌표의 차가 아니면서 실제 MST 중 하나의 구성 에지라면 두 노드 사이의 거리를 구하게 되는 방향의 축으로 정렬할 때 인접하게 될 것이기 때문이다. (위의 "관찰의 증명"을 생각해보자.) 이 설명은 x를 y 또는 z로 바꿔도 마찬가지로 통한다.
아래는 제출한 소스코드이다.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct P {
int x, y, z;
int idx;
};
struct E {
long long w;
int idx1, idx2;
};
bool cmpx(P p1, P p2) {
if (p1.x < p2.x) return 1;
else return 0;
}
bool cmpy(P p1, P p2) {
if (p1.y < p2.y) return 1;
else return 0;
}
bool cmpz(P p1, P p2) {
if (p1.z < p2.z) return 1;
else return 0;
}
bool cmpw(E e1, E e2) {
if (e1.w < e2.w) return 1;
else return 0;
}
int dset[100000];
int findf(int x) {
if (x == dset[x]) return x;
else return dset[x] = findf(dset[x]);
}
P arr[100000];
vector<E> edge;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int N; cin >> N;
for (int i = 0; i < N; i++) {
dset[i] = i;
}
for (int i = 0; i < N; i++) {
int x, y, z; cin >> x >> y >> z;
arr[i].x = x, arr[i].y = y, arr[i].z = z;
arr[i].idx = i;
}
sort(arr, arr + N, cmpx);
for (int i = 1; i < N; i++) {
E e;
e.w = arr[i].x - arr[i - 1].x;
e.idx1 = arr[i].idx;
e.idx2 = arr[i - 1].idx;
edge.push_back(e);
}
sort(arr, arr + N, cmpy);
for (int i = 1; i < N; i++) {
E e;
e.w = arr[i].y - arr[i - 1].y;
e.idx1 = arr[i].idx;
e.idx2 = arr[i - 1].idx;
edge.push_back(e);
}
sort(arr, arr + N, cmpz);
for (int i = 1; i < N; i++) {
E e;
e.w = arr[i].z - arr[i - 1].z;
e.idx1 = arr[i].idx;
e.idx2 = arr[i - 1].idx;
edge.push_back(e);
}
sort(edge.begin(), edge.end(), cmpw);
long long ans = 0;
for (auto e : edge) {
int x = findf(e.idx1), y = findf(e.idx2);
if (x == y) continue;
dset[y] = x;
ans += e.w;
}
cout << ans;
}
'BOJ' 카테고리의 다른 글
[BOJ 2628 // C++] 종이자르기 (0) | 2021.05.25 |
---|---|
[BOJ 2473 // C++] 세 용액 (0) | 2021.05.24 |
[BOJ 1028 // C++] 다이아몬드 광산 (0) | 2021.05.22 |
[BOJ 21600 // C++] 계단 (0) | 2021.05.21 |
[BOJ 21599 // C++] 아이템 배치하기 (0) | 2021.05.20 |