이번 문제는 크루스칼 알고리즘을 이용해서 풀었습니다.
https://www.acmicpc.net/problem/2887
하지만, V개의 행성을 모두 연결한다면, \(V(V-1)\)만큼의 간선이 생기므로, 정렬을 할 때, 주어진 V값에 대해서 느려질 수 있습니다.
간선의 갯수는 최소 비용 신장 트리(Minimum Spanning Tree)를 프림 알고리즘을 이용하든, 크루스칼 알고리즘을 이용하든 모두 시간 복잡도가 주어진 V값의 최대치라면, 시간초과가 됩니다. \(E = V(V-1)\)이 된다면, 프림 알고리즘 상에서는 \(O(E \log V) = O(V^2 \log V)\), 크루스칼 알고리즘 상에서는 \(O(E \log E) = O(V^2 \log V\) 형태가 되겠죠. 물론 프림 알고리즘을 구현할 때 피보나치 힙을 구성하면 더 빠르게 할 수 있습니다.
그래서 가장 중요한 알고리즘은 거리를 계산하는 공식의 특성에서 찾아서 했습니다. 각 축 중 가장 적은 값을 이용하므로, 각 축별로 행성을 정렬한 후에 이웃하는 것끼리 간선을 만듭니다. 겹치는 간선이 있을 수 있지만 그것은 무시합니다.
문제 분석 및 해결 방법
문제의 본질은 3차원 공간에 있는 n개의 행성을 최소 비용으로 연결하는 것입니다. 이를 해결하기 위해 다음과 같은 접근 방식을 사용합니다.
1. **행성 간 거리 계산**:
행성 간의 거리는 터널을 구축하는 데 드는 비용으로 간주되며, 이 거리는 행성 간 좌표 차이의 절대값 중 가장 작은 값으로 정의됩니다. 예를 들어, 두 행성 \(A(x1, y1, z1)\)와 \(B(x2, y2, z2)\) 사이의 거리는 다음과 같이 계산됩니다:
\[
\text{DISTANCE}(A, B) = \min(|x1 - x2|, |y1 - y2|, |z1 - z2|)
\]
2. **간선 생성**:
행성들을 연결하기 위한 간선(Edge)은 행성 간의 거리로 정의되며, 이를 기준으로 간선 목록을 생성합니다. 주어진 좌표를 기준으로 각 축(x, y, z)별로 정렬하여 인접한 행성들 간의 거리로 간선을 생성합니다. 이렇게 하면 \(n-1\)개의 간선이 각 축에 대해 만들어지므로 총 3(n-1)개의 간선이 생성됩니다.
3. **최소 스패닝 트리 구성**:
크루스칼 알고리즘(Kruskal's Algorithm)을 사용하여 최소 스패닝 트리를 구성합니다. 크루스칼 알고리즘은 간선을 가중치 오름차순으로 정렬한 후, 가장 낮은 가중치의 간선부터 차례로 선택하면서 사이클이 생기지 않도록 간선을 추가하는 방식으로 동작합니다.
이 과정에서 서로소 집합(Union-Find) 자료구조를 이용해 간선이 사이클을 형성하는지 여부를 확인합니다. 각 행성은 처음에 자기 자신을 부모로 가지며, 연결된 행성들의 집합은 같은 부모를 공유하게 됩니다.
코드 상세 설명
1. **구조체 정의**:
struct Coord { int p, x, y, z; } c[100000];
struct Edge { int a, b, c; } e[300000];
- `Coord` 구조체는 행성의 좌표와 행성의 번호를 저장합니다.
- `Edge` 구조체는 두 행성 사이의 간선을 나타내며, `a`와 `b`는 연결된 두 행성의 번호, `c`는 이들 간의 거리(즉, 비용)를 저장합니다.
2. **정렬 함수 정의**:
bool cmpx(const Coord &a, const Coord &b) { return a.x < b.x; }
bool cmpy(const Coord &a, const Coord &b) { return a.y < b.y; }
bool cmpz(const Coord &a, const Coord &b) { return a.z < b.z; }
bool cmp(const Edge &a, const Edge &b) { return a.c < b.c; }
각 축에 대해 좌표를 기준으로 행성을 정렬하기 위한 비교 함수와, 간선을 비용 기준으로 정렬하기 위한 비교 함수입니다.
3. **거리 계산 함수**:
int DISTANCE(const Coord &a, const Coord &b)
{
int x=a.x-b.x, y=a.y-b.y, z=a.z-b.z;
x=x<0?-x:x; y=y<0?-y:y; z=z<0?-z:z;
if(x > y) x=y; if(x > z) x=z;
return x;
}
두 행성 간의 거리를 계산합니다. 거리 계산은 위에서 언급한 최소 절대값을 기반으로 합니다.
4. **메인 함수**:
int main()
{
int n, v[100000], cnt=1, idx=0;
scanf("%d", &n);
for(int i=0;i<n;i++) v[i]=i,c[i].p=i, scanf("%d%d%d",&c[i].x,&c[i].y,&c[i].z);
std::sort(c,c+n,cmpx);
for(int i=0;i<n-1;i++,idx++) e[idx].a=c[i].p, e[idx].b=c[i+1].p,
e[idx].c=DISTANCE(c[i], c[i+1]);
std::sort(c,c+n,cmpy);
for(int i=0;i<n-1;i++,idx++) e[idx].a=c[i].p, e[idx].b=c[i+1].p,
e[idx].c=DISTANCE(c[i], c[i+1]);
std::sort(c,c+n,cmpz);
for(int i=0;i<n-1;i++,idx++) e[idx].a=c[i].p, e[idx].b=c[i+1].p,
e[idx].c=DISTANCE(c[i], c[i+1]);
std::sort(e,e+idx,cmp);
long long ans=0;
for(int i=0; cnt < n; i++)
{
int a=e[i].a, b=e[i].b;
while(v[a]!=a) a=v[a]; while(v[b]!=b) b=v[b];
if(a==b) continue;
v[b]=v[e[i].a]=v[e[i].b]=a;
ans+=e[i].c; cnt++;
}
printf("%lld\n", ans);
return 0;
}
- 행성의 수 `n`을 입력받고, 각 행성의 좌표를 입력받습니다.
- 각 축에 대해 행성을 정렬하고, 인접한 행성 간의 거리를 계산해 간선 리스트를 만듭니다.
- 생성된 간선 리스트를 거리를 기준으로 정렬한 후, 크루스칼 알고리즘을 통해 최소 스패닝 트리를 구성합니다.
- 최종적으로 최소 스패닝 트리의 총 비용을 출력합니다.
'Programming > BOJ' 카테고리의 다른 글
[C/C++] 백준 #3013 부분 수열의 중앙값(누적 합) (0) | 2024.09.30 |
---|---|
[C/C++] 백준 #2981 검문(유클리드 호제법) (0) | 2024.09.20 |
[C/C++] 백준 #2877 4와 7(수학) (0) | 2024.08.23 |
[Python]백준 #2824 최대공약수(큰수자료구조) (0) | 2024.08.18 |
[Python] 백준 #2812 크게 만들기(탐욕 알고리즘) (0) | 2024.08.17 |
댓글