백준 문제/MST

[백준] 4386번 별자리 만들기

dubu0721 2024. 11. 12. 13:13

문제: 4386번: 별자리 만들기

 

#include <map>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>

using namespace std;

vector<int> nodes;
vector<pair<float, float>> coordinates; // 좌표값들(좌푤를 노드와 매핑하는데 이용할 것)

// 두 번째 요소인 가중치를 기준으로 최소힙으로 동작하도록 하기 위한 사용자 정의 함수 객체
struct compare {
	// 우선 순위 큐의 디폴트는 최대힙으로 동작
	// 이를 최소힙으로 동작하도록 해주려면 a>b 이를 리턴해줘야함.
	// 근데 지금 두번째 요소를 기준으로 최소힙으로 만드니까 a.second > b.second 이렇게 한 것!!
	bool operator()(pair<pair<int, int>, float>& a, pair<pair<int, int>, float>& b) {
		return a.second > b.second;
	}
};

int find_set(int u) {
	if (nodes[u] == u)
		return u;
	return nodes[u] = find_set(nodes[u]);
}

// 이 함수에 진입했다는 건 이미 두 정점을 연결해도 괜찮다는 판단이 전에 나온것
// 즉, 이 경우에는 연결해도 사이클 안 생기니까 그냥 연결해주기
void union_(int vr, int ur) {
	nodes[ur] = vr; // 연결!
}

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

	int n; // 별의 개수
	cin >> n;
	nodes = vector<int>(n); // 노드의 개수는 별의 개수와 같음
	for (int i = 0; i < n; i++)
		nodes[i] = i;

	for (int i = 0; i < n; i++) {
		float x, y; // 별의 좌표
		cin >> x >> y;

		pair<float, float> coordinate = make_pair(x, y);
		// 좌표들 저장하는 벡터에 저장(얘의 0번 인덱스 요소가 0번 노드)
		coordinates.push_back(coordinate); 
	}


	// 간선 저장할 우선 순위 큐(최소힙으로 동작하도록..)
	// 가중치를 기준으로 최소힙으로 동작하도록 하고 싶음..
	// 아 처음에 비교 함수로 함수 포인터를 주려고 했는데 안된대여.. 대신에 비교 함수 객체를 사용해야함.
	// 안되는 이유도 찾아봤는데 c++ 템플릿 시스템이 prioiry_queue 의 비교 정책을 타입으로 받아들임
	// 근데 일반 함수는 타입이 아니니까 템플릿 인자로 바로 전달할 수 없음..
	// 대신 구조체나 클래스를 사용해서 그 안에 operator() 를 오버로딩 하면, 이를 함수 객체로서 전달 가능
	priority_queue<pair<pair<int, int>, float>, vector<pair<pair<int, int>, float>>, compare> edges;

	// 일단 모든 정점들끼리의 간선을 만들어줄 것.
	// 이는 시간복잡도가 O(n**2) 인데 n 의 최대 범위가 100까지니까 괜찮음
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			// 자기 자신이랑은 거리 계산할 필요 없는거 아님?
			if (i == j) continue;

			pair<float, float> a = coordinates[i]; // 좌표 1
			pair<float, float> b = coordinates[j]; // 좌표 2

			// 두 좌표의 거리 계산
			float distance = sqrt(((a.first - b.first) * (a.first - b.first) + (a.second - b.second) * (a.second - b.second)));
			pair<pair<int, int>, float> edge = make_pair(make_pair(i, j), distance); // 정점이랑 가중치를 묶어서 간선 저장 최소힙에 저장
			edges.push(edge); // 힙에 넣어용..
		}
	}

	int edgeCnt = 0; // 이 값이 (노드 개수 - 1) 보다 작을때까지 반복문 돌 것..
	float sum = 0;
	while (edgeCnt < (n - 1) && !edges.empty()) {
		pair<pair<int, int>, float> minEdge = edges.top();
		edges.pop(); // 꺼내야 하니까 pop 해주기

		// 정점이 이미 연결되어 있는데 또 연결하려고 하면 사이클이니까 이 경우는 패스!
		int ur = find_set(minEdge.first.first);
		int vr = find_set(minEdge.first.second);
		if (ur != vr) {
			union_(ur, vr); // 연결
			sum += minEdge.second; // 가중치 더해주기..
			edgeCnt++;
		}
	}
	cout << sum << "\n";

	return 0;
}

 

우선 어제 풀었던 1197번과 다른 점은 입력이 정점 번호가 아니라 좌표값으로 들어온다는 점과, 가중치를 직접 주던 전과는 다르게 직접 계산을 해줘야 한다는 것이다.  그리고 1197번은 각 정점 사이의 간선 정보도 주었는데 이를 주지 않아서 간선도 직접 만들어줘야 했다.

 

각 정점을 다른 모든 정점과 연결해 줘도 되겠다는 생각을 한 건 입력 조건을 보고 난 후였다. 일단 지금 별의 개수인 n 의 최대 범위가 100밖에 안 되므로 다른 모든 정점과 연결해줘도(시간복잡도 O(n^2)) 시간이 충분하겠다는 판단이 들었다.

 

그리고 이 문제의 또 중요한 점은 입력 받은 좌표를 정점 번호로 매핑해줘야 한다는 것인데 간단하게 해결할 수 있다. 일단 coordinates 라는 좌표값들을 저장할 벡터를 하나 만들었다. 이는 좌표를 노드와 매핑하는데 이용하는데 사용법은 다음과 같다. 그냥 단순히 coordinates 벡터의 0번째 요소가 0번 정점이라고 생각하는 것이다.

 

이제 또 중요한 점은 간선들을 저장해놓을 우선 순위 큐를 만드는 것이었다. 일단 최소힙으로 동작해야 하고, pair 의 두번째 요소인 가중치를 기준으로 최소힙을 만들고 싶었다. 이를 위해서 처음에 compare 라는 함수를 만들어서 priority_queue 의 비교 함수로 전달하려고 했는데 실패했다. 이유는 우선 순위 큐의 비교 함수로 함수 포인터를 주는게 불가능해서...

대신에 비교 함수 객체를 사용하면 됐다. 그래서 기존에 만들었던 compare 함수 대신에 struct 로 만들고, operator() 를 오버로딩 해서 똑같은 기능을 하도록 만들었다.

 

이중 반복문을 돌면서 좌표 1과 좌표 2 사이의 거리를 계산했다. 이는 가중치를 의미한다. 아까 말했던 것처럼 coordinates 의 인덱스가 곧 정점 번호를 의미한다. 그래서 간선을 만들 때, pair<int, int> 정점 번호의 쌍으로 현재 for 문 당시의 인덱스 번호를 넘겨주었다. 

 

모든 간선들을 저장하고 난 다음에는 기존에 하던 방식대로 해결하면 된다. 현재까지 선택된 간선의 개수가 (노드의 개수 - 1) 보다 작을 때까지 반복문을 돌도록 하고, 각 반복문 마다 최소 간선을 하나 꺼낸다음 이 간선의 두 정점이 이미 연결되어 있는지 여부를 판단하고, 아니라면 연결해준다음 가중치를 sum 값에 더해주면 쉽게 해결할 수 있다.

 

재밌었다 ^0^

 

 

 

참고자료:

[C언어/C++] pow, sqrt 함수에 대해서(루트함수, 제곱, 제곱근)

 

[C언어/C++] pow, sqrt 함수에 대해서(루트함수, 제곱, 제곱근)

안녕하세요. BlockDMask 입니다 오늘은 (저는) 자주 쓰지는 않지만 꼭 알아둬야하는 함수를 두개 묶어서 가지고왔습니다. 바로 pow, sqrt 함수인데요. 중학교때 제곱과 제곱근(루트) 배우셨죠? 그걸이

blockdmask.tistory.com

'백준 문제 > MST' 카테고리의 다른 글

[백준] 16398번 행성 연결  (1) 2024.12.06
[백준] 9372번 상근이의 여행  (1) 2024.12.06
[백준] 1774번 우주신과의 교감  (0) 2024.11.14
[백준] 16562번 친구비  (0) 2024.11.13
[백준] 1197번 최소 스패닝 트리  (2) 2024.11.11