문제
https://www.acmicpc.net/problem/2887
2887번: 행성 터널
첫째 줄에 행성의 개수 N이 주어진다. (1 ≤ N ≤ 100,000) 다음 N개 줄에는 각 행성의 x, y, z좌표가 주어진다. 좌표는 -109보다 크거나 같고, 109보다 작거나 같은 정수이다. 한 위치에 행성이 두 개 이
www.acmicpc.net
구현 방법
최소 스패닝 문제 이지만!!!!!
주어진 행성의 개수가 100,000개, 모든 행성 간의 거리를 구하면 100,000 * 99,999/2 = 약 500억개 이므로 사용하는 메모리가 메모리 제한인 128MB를 훨씬 초과하게 됩니다.
따라서 다른 방법을 찾아야 했지만 전 찾지 못했고^^ 질문 검색을 참고하여 풀었습니다.
행성을 터널로 연결할 때 드는 비용은 X 좌표 차이의 절대값, Y 좌표 차이의 절대값, Z 좌표 차이의 절대값 중 최소이니 3가지 좌표를 한 번에 계산 할 필요가 없습니다.
약간 플랫하게 X좌표만 생각해 봤을 때 주어진 행성들의 X 좌표를 비용 순으로 정렬하여 각 인접한 행성을 이어준다면 어떨까요?!?!?!?
예제를 예로 들어 X좌표를 비용 순으로 정렬한다면 -1(C) 10(D) 11(A) 14(B) 19(E) 입니다.
만약 C와 A 행성에 터널을 놓는다면 드는 비용은 12가 되겠죠.
하지만 C와 A 행성 사이에 D 행성이 존재하기 때문에 C-D, D-A에 터널을 놓는 것이 더 최소 비용으로 모든 행성을 연결할 수 있습니다.
따라서 좌표마다 비용 순으로 정렬하여 인접한 행성들로 간선을 만든 후 Kruskal 알고리즘을 사용하면 되겠죠?!
마찬가지로 Y좌표, Z좌표도 비용 순으로 정렬하여 리스트에 저장한다면 (100,000-1) * 3 = 약 30만개로 메모리를 훨씬 적게 사용하게 됩니다.
아래 그림은 예제를 위에 설명한 방식으로 푼 정리 입니다.
구현 코드
import java.io.*;
import java.util.*;
public class Main {
static int N, root[], cnt;
static Value from, to;
static List<Value> X = new ArrayList<>(), Y = new ArrayList<>(), Z = new ArrayList<>();
static List<Edge> edgeList = new ArrayList<>();
static class Edge implements Comparable<Edge>{ // 간선 저장할 클래스
int from, to, weight;
public Edge(int from, int to, int weight) {
super();
this.from = from;
this.to = to;
this.weight = weight;
}
@Override
public int compareTo(Edge o) {
return Integer.compare(this.weight, o.weight);
}
}
static class Value implements Comparable<Value> { // 좌표 저장할 클래스
int num, value;
public Value(int num, int value) {
super();
this.num = num;
this.value = value;
}
@Override
public int compareTo(Value o) {
return Integer.compare(this.value, o.value);
}
}
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
N = Integer.parseInt(br.readLine());
root = new int[N+1];
StringTokenizer st = null;
for (int i=0;i<N;i++) {
st = new StringTokenizer(br.readLine());
// 각 좌표들 저장
X.add(new Value(i, Integer.parseInt(st.nextToken())));
Y.add(new Value(i, Integer.parseInt(st.nextToken())));
Z.add(new Value(i, Integer.parseInt(st.nextToken())));
}
// 좌표를 비용 순으로 정렬
Collections.sort(X);
Collections.sort(Y);
Collections.sort(Z);
for (int i=0;i<N-1;i++) {
// 각 좌표별로 인접한 터널 생성
from = X.get(i); to = X.get(i+1);
edgeList.add(new Edge(from.num, to.num, Math.abs(from.value-to.value)));
from = Y.get(i); to = Y.get(i+1);
edgeList.add(new Edge(from.num, to.num, Math.abs(from.value-to.value)));
from = Z.get(i); to = Z.get(i+1);
edgeList.add(new Edge(from.num, to.num, Math.abs(from.value-to.value)));
}
long ans = 0;
makeSet();
Collections.sort(edgeList); // 간선 리스트 비용 순으로 정렬
for (Edge edge : edgeList) {
if (union(edge.from, edge.to)) { // 만약 연결되지 않았다면 연결해주고
ans += edge.weight; // 비용 더하기
if (++cnt == N-1) { // 모든 행성을 연결하면 끝
System.out.println(ans);
System.exit(0);
}
}
}
}
static void makeSet() {
for (int i=0;i<N+1;i++) {
// 초기에 아무것도 연결되지 않았으니 -1로 초기화
root[i] = -1;
}
}
static int findSet(int x) {
if (root[x] < 0) return x; // 만약 0보다 작다 = 연결되지 않았다 root는 자기 자신
return root[x] = findSet(root[x]); // 연결된 상단의 루트를 찾아서 리턴
}
static boolean union(int a, int b) {
int aRoot = findSet(a);
int bRoot = findSet(b);
if (aRoot == bRoot) return false; // 루트가 같다 == 연결되었다
root[bRoot] = aRoot; // 루트를 같게 한다 == 연결
return true;
}
}
'백준' 카테고리의 다른 글
백준 9470번 : Strahler 순서 (Java) (0) | 2022.02.10 |
---|---|
백준 14567번 : 선수과목 (Prerequisite) (Java) (0) | 2022.02.09 |
백준 1113번 : 수영장 만들기 (0) | 2021.07.23 |
백준 20057번 : 마법사 상어와 토네이도 (0) | 2021.07.21 |
백준 5212번 : 지구 온난화 (0) | 2021.07.20 |