백준 1774 우주신과의 교감

·2022년 8월 29일
0

문제

문제 설명

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

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

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

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

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


Java

import java.io.*;
import java.util.*;

public class Main {
    //두 (x, y) 좌표 간의 거리 계산 함수
    public static double calculate(int[] a, int[] b){
        return Math.sqrt(Math.pow(a[0]-b[0], 2)+Math.pow(a[1]-b[1], 2));
    }

    //부모 찾기 함수
    public static int findUnion(int[] union, int x){
        if(union[x]!=x)
            union[x]=findUnion(union, union[x]);

        return union[x];
    }

    public static void main(String[] args)throws IOException{
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out));

        StringTokenizer st=new StringTokenizer(br.readLine());
        int n=Integer.parseInt(st.nextToken());
        int m=Integer.parseInt(st.nextToken());

        //(x, y) 좌표 저장용 HashMap
        Map<Integer, int[]> location=new HashMap<>();
        for(int i=0; i<n; i++){
            st=new StringTokenizer(br.readLine());
            int x=Integer.parseInt(st.nextToken());
            int y=Integer.parseInt(st.nextToken());
            //인덱스를 Key값으로 좌표 저장
            location.put(i, new int[]{x, y});
        }

        //union 배열 초기화(자기 자신이 부모)
        int[] union=new int[n];
        for(int i=0; i<n; i++)
            union[i]=i;

        //이미 연결된 노드들에 대해서 union 합치기
        for(int i=0; i<m; i++){
            st=new StringTokenizer(br.readLine());
            int a=findUnion(union, Integer.parseInt(st.nextToken())-1);
            int b=findUnion(union, Integer.parseInt(st.nextToken())-1);

            int parent=Math.min(a, b);
            int child=Math.max(a, b);

            union[child]=parent;
        }
        
        //크루스칼 알고리즘을 위해 우선순위 큐 선언, [노드 a, 노드 b, 거리]의 형태이고 거리를 기준으로 정렬
        PriorityQueue<double[]> pq=new PriorityQueue<>(Comparator.comparingDouble(o->o[2]));
        for(int i=0; i<n-1; i++){
            for(int j=i+1; j<n; j++){
                //모든 노드들 간 부모가 다르면 둘 사이를 잇는 간선의 정보 우선순위 큐에 저장
                if(findUnion(union, i)!=findUnion(union, j)) {
                    int[] a = location.get(i);
                    int[] b = location.get(j);

                    pq.add(new double[]{i, j, calculate(a, b)});
                }
            }
        }

        //set 관련: 필요한 간선 갯수를 구하기 위한 집합 종류의 갯수 구하기
        Set<Integer> set=new HashSet<>();
        Arrays.stream(union).forEach(o->set.add(o));
        int count=set.size();
        
        double result=0;
        while(count>1){
            double[] ptr=pq.poll();
            int a=findUnion(union, (int)ptr[0]);
            int b=findUnion(union, (int)ptr[1]);

            if(a==b)
                continue;

            union[Math.max(a, b)]=Math.min(a, b);
            result+=ptr[2];
            count--;
        }

        //소수점 둘째자리까지 출력
        System.out.println(String.format("%.2f", result));
    }
}

해결 과정

  1. Kruskal 또는 Prim 알고리즘으로 모두 연결하려고 했지만 두 정점 간의 비용이 주어지지 않았다. 따라서 calculate 함수를 선언해서 두 X, Y 좌표 간 거리를 구하고, 연결되어 있지 않은 모든 노드들 간의 가능한 간선을 Priority Queue에 넣었다. 최대 노드의 갯수는 1000이므로 비용을 최대로 구해도 100만번의 calculate 연산을 하면 된다. 연결해야 하는 집합의 갯수를 구하기 위해 Set을 사용해서 모든 부모들의 수를 세줬다.

  2. 😁

profile
渽晛

0개의 댓글