[백준] 16562번 친구비

2024. 11. 13. 12:28·백준 문제/MST

문제: 16562번: 친구비

 

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

using namespace std;

vector<int> nodes; // 정점들

// 우선 순위 큐가 pair 의 두 번째 요소를 기준으로 최소힙으로 동작할 수 있도록 하는 비교 함수 객체 만들기
struct compare {
	bool operator()(pair<pair<int, int>, int> a, pair<pair<int, int>, int> 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 ur, int vr) {
	nodes[vr] = ur; // 연결!
}

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

	int n, m, k;  // n:학생수, m:친구관계수, k:가진돈
	cin >> n >> m >> k;
	
	// 인덱스 계산 편하게 하려고 n+1 만큼 공간 잡음.
	// 0번 인덱스 요소는 안 쓸 것..
	vector<int> frdValue(n + 1); // 친구비
	for (int i = 1; i < n+1; i++)
		cin >> frdValue[i];

	nodes = vector<int>(n + 1); // 0번은 준석이, 1번부터 n번까지 친구들
	for (int i = 0; i < n + 1; i++)
		nodes[i] = i; // 각 정점이 속한 집합 번호 반영
	

	int selectedEdgeCnt = 0;
	for (int i = 0; i < m; i++) {
		int v, w; // 학생 v, w
		cin >> v >> w;

		int vr = find_set(v);
		int wr = find_set(w);
		
		// 자기자신으로의 연결이랑 중복 연결은 카운트로 안 셀거임
		// 즉, 위 경우가 아닐 때만 연결하고 현재까지 선택된 간선의 수 + 1 해주기
		if (vr != wr) {
			union_(vr, wr);
			selectedEdgeCnt++;
		}

		union_(v, w); // 친구관계 연결해놓기
	}

	// 준석이가 다른 친구들이랑 친구할 때 필요한 비용을 가중치로 생각
	// 우선 순위 큐에는 준석이와 다른 친구랑 직접 친구하는 관계만 넣어놓을 것..
	// 최소힙으로 동작하도록..
	priority_queue<pair<pair<int, int>, int>, vector<pair<pair<int, int>, int>>, compare> edges;
	for (int i = 1; i < n + 1; i++) {
		// '준석이가 i번 친구와 친구하는데 드는 비용' 정보를 담아 간선을 만들기
		pair<pair<int, int>, int> edge = make_pair(make_pair(0, i), frdValue[i]);
		edges.push(edge);
	}

	int sum = 0;
	// 현재 총 노드의 수는 n+1 개임. MST 구할 때 간선은 n개까지만 선택되어야함.
	while (selectedEdgeCnt < n && !edges.empty()) {
		// 일단 가중치가 가장 낮은 간선부터 가져옴
		pair<pair<int, int>, int> minEdge = edges.top();
		edges.pop(); // 가져왔으니까 빼야함

		// 연결되어 있는 경우가 아닐 때만 연결..
		int ur = find_set(minEdge.first.first);
		int vr = find_set(minEdge.first.second);
		if (ur != vr) {
			union_(ur, vr);
			sum += minEdge.second; // 친구비 더해주기
			selectedEdgeCnt++; // 선택된 간선 수 + 1
		}
	}

	// 필요한 친구비 총액이 현재 가진 돈보다 작거나 같으면 가능
	// 돈 부족하면 Oh no 출력..
	if (sum <= k)
		cout << sum << "\n";
	else
		cout << "Oh no\n";


	return 0;
}

 

일단 문제를 어떤식으로 풀었나면 입력으로 받은 친구 관계는 선택된 간선으로 취급하고, 준석이와 나머지 친구들의 직접적인 친구관계를 우선 순위 큐에 넣어놓은 다음 현재 선택된 간선의 개수가 n 보다 작을 때까지 간선을 추가하도록 했다.

 

왜 n 보다 작을 때까지 간선을 추가하도록 해야하냐면 입력으로 n 개의 친구(=정점)가 들어오기는 했지만 준석이까지 정점으로 취급해야 하기 때문에 총 n+1 개의 정점이 있기 때문이다. MST 에서 간선의 개수는 정점의 개수 - 1개이므로 위의 조건을 얻었다.

 

처음에는 입력으로 주어지는 친구 관계를 집합이나 벡터에 넣어 놓은 후 MST 로 처리하는 방법을 생각했는데(깊게 생각하진 않았다. 그래서 쓸 말이 없음) 이보다 더 나은 방법이 떠올랐다. 

 

일단 입력으로 주어지는 친구관계는 무조건 MST 에 포함되어야 한다. 이 조건을 어떤식으로 처리했냐면, 입력으로 친구관계를 받는 시점에 union 을 해준다. 근데 두 정점을 그냥 무조건 합치는 건 안 되고, 자기 자신으로의 연결이랑 중복 연결은 카운트로 안 셀거니까 이 경우를 제외하고 합쳐야 한다. 

 

근데 위와 같은 상황은 그냥 두 정점이 속한 집합의 번호가 다를 경우에 합쳐주는 것과 똑같았다. 그래서 이렇게 합치는 경우에 selectedEdgeCnt 의 값을 +1 해줘서 나중에 MST 계산할 때 반복 횟수를 조정해주었다.

 

이제 다음으로는 준석이랑 다른 친구들의 직접적인 친구관계를 우선 순위 큐에 넣어놨다(최소힙으로 동작하도록). 이렇게 간선들을 다 만든 후 큐에 다 넣어놨으면 본격적으로 Kruskal's Algorithm 을 적용하면 문제가 풀린다. 

 

selectedEdgeCnt 가 n 보다 작을 때까지 반복문을 돌도록 하고, 각 반복문마다 최소 비용 간선을 가져와서 두 정점을 연결하는 것이다. 연결 조건이 만족되면 sum 값의 간선의 가중치(==친구비) 를 더해주고, selectedEdgeCnt 의 값도 +1 해주면 된다. 여기서도 위와 마찬가지로 두 정점이 이미 연결되어 있으면 포기하고 다음 간선으로 넘어간다.

 

맨 마지막으로 해줘야 하는 일은 현재 필요한 총 친구비가 가진 돈보다 작은지 아닌지를 판단해서 출력의 경우를 나눠주는 것이다. 만약 친구비 총액이 현재 가진 돈보다 작거나 같으면 구한 친구비 총액을 출력하도록 하고, 아닌 경우에는 Oh no 를 출력하도록 했다. 

 

근데 나는 이 문제가 MST 로 풀어야 하는 건줄 알았는데 풀고 나서 다른 사람들 풀이 보니까 MST 를 안 쓰고 할 수 있었다. 다른 방법으로 푸는 방식도 알아둬야 할 것 같다.

 

 

 

참고자료:

[C++] 백준/BOJ - 16562 : 친구비

 

[C++] 백준/BOJ - 16562 : 친구비

문제 이해 단계 https://www.acmicpc.net/problem/16562 16562번: 친구비 첫 줄에 학생 수 N (1 ≤ N ≤ 10,000)과 친구관계 수 M (0 ≤ M ≤ 10,000), 가지고 있는 돈 k (1 ≤ k ≤ 10,000,000)가 주어진다. 두번째 줄에 N개

howudong.tistory.com

 

'백준 문제/MST' 카테고리의 다른 글
  • [백준] 9372번 상근이의 여행
  • [백준] 1774번 우주신과의 교감
  • [백준] 4386번 별자리 만들기
  • [백준] 1197번 최소 스패닝 트리
dubu0721
dubu0721
dubu0721 님의 블로그 입니다.
  • dubu0721
    dubu0721 님의 블로그
    dubu0721
  • 전체
    오늘
    어제
    • 분류 전체보기 (334)
      • 프로그래밍언어론 정리 (0)
      • 컴퓨터네트워크 정리 (5)
      • 알고리즘&자료구조 공부 (64)
        • it 취업을 위한 알고리즘 문제풀이 입문 강의 (60)
        • 학교 알고리즘 수업 (3)
        • 실전프로젝트I (0)
      • 백준 문제 (193)
        • 이분탐색 (7)
        • 투포인트 (10)
        • 그래프 (7)
        • 그리디 (24)
        • DP (25)
        • BFS (15)
        • MST (7)
        • KMP (4)
        • Dijkstra (3)
        • Disjoints Set (4)
        • Bellman-Ford (2)
        • 시뮬레이션 (3)
        • 백트래킹 (15)
        • 위상정렬 (5)
        • 자료구조 (25)
        • 기하학 (1)
        • 정렬 (11)
        • 구현 (8)
        • 재귀 (8)
        • 수학 (8)
      • 유니티 공부 (11)
        • 레트로의 유니티 게임 프로그래밍 에센스 (11)
        • 유니티 스터디 자료 (0)
        • C# 공부 (0)
      • 유니티 프로젝트 (48)
        • 케이크게임 (13)
        • 점토게임 (35)
      • 언리얼 공부 (10)
        • 이득우의 언리얼 프로그래밍 (10)
      • 진로 (1)
      • 논문 읽기 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    백트래킹
    재귀
    해시
    티스토리챌린지
    우선순위큐
    오블완
    dp
    이벤트 트리거
    백준
    언리얼
    투포인터
    레트로의 유니티 프로그래밍
    수학
    스택
    유니티
    자료구조
    유니티 프로젝트
    C#
    그래프
    유니티 공부 정리
    이분탐색
    맵
    큐
    골드메탈
    정렬
    바킹독
    BFS
    그리디
    시뮬레이션
    이득우
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
dubu0721
[백준] 16562번 친구비
상단으로

티스토리툴바