[백준] 1774 우주신과의 교감 (C++)

우리누리·2024년 7월 8일

👓 문제 설명


황선자씨는 우주신과 교감을 할수 있는 채널러 이다. 하지만 우주신은 하나만 있는 것이 아니기때문에 황선자 씨는 매번 여럿의 우주신과 교감하느라 힘이 든다. 이러던 와중에 새로운 우주신들이 황선자씨를 이용하게 되었다.

하지만 위대한 우주신들은 바로 황선자씨와 연결될 필요가 없다. 이미 황선자씨와 혹은 이미 우주신끼리 교감할 수 있는 우주신들이 있기 때문에 새로운 우주신들은 그 우주신들을 거쳐서 황선자 씨와 교감을 할 수 있다.

우주신들과의 교감은 우주신들과 황선자씨 혹은 우주신들 끼리 이어진 정신적인 통로를 통해 이루어 진다. 하지만 우주신들과 교감하는 것은 힘든 일이기 때문에 황선자씨는 이런 통로들이 긴 것을 좋아하지 않는다. 왜냐하면 통로들이 길 수록 더 힘이 들기 때문이다.

또한 우리들은 3차원 좌표계로 나타낼 수 있는 세상에 살고 있지만 우주신들과 황선자씨는 2차원 좌표계로 나타낼 수 있는 세상에 살고 있다. 통로들의 길이는 2차원 좌표계상의 거리와 같다.

이미 황선자씨와 연결된, 혹은 우주신들과 연결된 통로들이 존재한다. 우리는 황선자 씨를 도와 아직 연결이 되지 않은 우주신들을 연결해 드려야 한다. 새로 만들어야 할 정신적인 통로의 길이들이 합이 최소가 되게 통로를 만들어 “빵상”을 외칠수 있게 도와주자.


💣 제한 사항

  • 첫째 줄에 우주신들의 개수 (N<=1000), 이미 연결된 신들과의 통로의 개수 (1<=M<=1000)가 주어진다.
  • 두 번째 줄부터 N개의 줄에는 황선자를 포함하여 우주신들의 좌표가 X, Y ( 0<=X, Y <= 1,000,000)가 주어진다. 그 밑으로 M개의 줄에는 이미 연결된 통로가 주어진다. 번호는 위의 입력받은 좌표들의 순서라고 생각하면 된다. 좌표는 정수이다.
  • 첫째 줄에 만들어야 할 최소의 통로 길이를 소수점 둘째 자리까지 반올림하여 출력하라.

🚨 접근 방법

최소 비용 신장 트리 (MST)의 문제이다.

정점과 간선이 따로 주어지지 않는다.

N개의 좌표가 주어지고, 이 좌표를 n-1개의 선으로 사이클 없이 연결할 때의 최소길이를 구하는 문제이다.

따라서, N개의 좌표에 해당하는 점들끼리 이을 수 있는 모든 간선의 길이를 구한다.
이후, 길이가 짧은 순서부터 정렬한다.

이후, M개의 이미 연결된 점들에 대해 setUnion을 수행한다.

연결된 간선의 개수를 포함해 n-1개의 선을 연결하면 종료한다.

(단, 이미 연결된 정보는 중복하여 들어올 수 있는 경우와 오름차순으로 주어지지 않음을 고려하지 않아서 바로 해결하지 못했다)

소수점 표현 방식

cout<<fixed -> 소수점 표현
cout.precision(2) -> .이후 2자리까지 표현 (반올림 자동 수행)


🚈 풀이

#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
#include <set>

using namespace std;
set<pair<int, int>>s;
int n, m;
int parents[1001];
bool isConnected[1001];
vector<pair<int, int>>connection;

struct Info {
	int n1, n2;
	double dis;
};
vector<pair<int, int>>pos;
vector<Info>infos;

struct Cmp {
	bool operator()(Info a, Info b) {
		return a.dis < b.dis;
	}
};

// 입력
void input() {
	cin >> n >> m;
	for (int i = 0; i < n; i++) {
		int x, y;
		cin >> x >> y;
		pos.push_back({ x,y });
	}

	for (int i = 0; i < m; i++) {
		int n1, n2;
		cin >> n1 >> n2;
		connection.push_back({ n1,n2 });
		isConnected[n1] = true;
		isConnected[n2] = true;
	}
	for (int i = 0; i < pos.size(); i++) {
		for (int j = i+1; j < pos.size(); j++) {
			
			if (i == j)continue;
			double dis = sqrt(pow(pos[i].first-pos[j].first,2)+pow(pos[i].second-pos[j].second,2));
			//cout << dis << "\n";
			infos.push_back({ i+1,j+1,dis });
		}
	}
	sort(infos.begin(), infos.end(), Cmp());
}

// 초기화(자신의 조상)
void init() {
	for (int i = 1; i <= n; i++) {
		parents[i] = i;
	}
}

// 정렬


// find
int find(int tar) {
	if (tar == parents[tar])return tar;
	int ret = find(parents[tar]);
	parents[tar] = ret;
	return ret;
}

// setUnion
void setUnion(int t1, int t2) {
	int f1 = find(t1);
	int f2 = find(t2);
	if (f1 == f2)return;
	parents[f2] = f1;
}

// 현재 연결 되어 있는 것 중에서
// 가장 조상을 기준으로 연결
int main() {
	input();
	init();
	int cnt = 0;
	for (int i = 0; i < connection.size(); i++) {
		int n1 = connection[i].first;
		int n2 = connection[i].second;
		if (find(n1) == find(n2))continue;
		if (n1 < n2) {
			setUnion(n1,n2);
		}
		else {
			setUnion(n2, n1);
		}
		cnt++;
	}
	int goal = n - 1;
	if (cnt == goal) {
		cout << 0.00;
		return 0;
	}
	double ans = 0;
	for (auto p : infos) {
		
		int ret1 = find(p.n1);
		int ret2 = find(p.n2);
		if (ret1==ret2) continue;
		setUnion(ret1, ret2);
		cnt++;
		ans += p.dis;
		if (cnt == goal)break;

	}
	cout << fixed;
	cout.precision(2);
	cout << ans;
	return 0;
}

0개의 댓글