본문 바로가기
알고리즘

[백준] 1774번: 우주신과의 교감 Java

by 랼랼 2022. 4. 26.

문제

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

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

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

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

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

 

입력

첫째 줄에 우주신들의 개수(N<=1,000) 이미 연결된 신들과의 통로의 개수(M<=1,000)가 주어진다.

두 번째 줄부터 N개의 줄에는 황선자를 포함하여 우주신들의 좌표가 (0<= X<=1,000,000), (0<=Y<=1,000,000)가 주어진다. 그 밑으로 M개의 줄에는 이미 연결된 통로가 주어진다. 번호는 위의 입력받은 좌표들의 순서라고 생각하면 된다. 좌표는 정수이다.

 

출력

첫째 줄에 만들어야 할 최소의 통로 길이를 출력하라. 출력은 소수점 둘째짜리까지 출력하여라.

 

https://www.acmicpc.net/problem/1774

 

1774번: 우주신과의 교감

(1,1) (3,1) (2,3) (4,3) 이렇게 우주신들과 황선자씨의 좌표가 주어졌고 1번하고 4번이 연결되어 있다. 그렇다면 1번하고 2번을 잇는 통로를 만들고 3번하고 4번을 잇는 통로를 만들면 신들과 선자씨끼

www.acmicpc.net

접근

최소신장트리 문제이다.

우주신의 2차원 좌표(x,y)가 주어지고 연결된 거리를 최소로 만들어야 한다.

다만 이미 서로 연결된 우주신들이 있어 이를 체크해야 한다.

 

kruskal MST를 이용하여 해결한다.

 

연결된 우주신끼리 부모관계를 설정하고,

이를 통해 서로 연결되지 않은 우주신 간선 정보를 List에 저장한다.

우주신 간선 정보 List를 간선이 짧은 순으로 정렬하고,

간선의 부모가 다른 경우,

간선을 선택하고, 두 우주신의 부모를 세팅한다. (트리 생성)

선택한 간선의 개수가 N-1이면 종료한다.

 

*출력 형식은 소수점 2자리 까지이다.

(이 부분 때문에 여러번 시도하게 되었다. 문제를 잘 읽어보도록 하자)

 

구현

Union Find를 위한 코드, kruskal 알고리즘을 사용하는데 필요하다

//부모 찾기
public static int getParent(int node) {
    if(Parents[node]==node) return node;
    return Parents[node]=getParent(Parents[node]);
}
//부모 세팅, 작은 수가 부모
public static void setParent(int node1, int node2) {
    int p1 = getParent(node1);
    int p2 = getParent(node2);
    if(p1<p2) Parents[p2]=p1;
    else Parents[p1]=p2;
}
//같은 부모 판별
public static boolean isSameParent(int node1, int node2) {
    return getParent(node1)==getParent(node2);
}

좌표 정보를 저장하는 God 클래스와 간선 정보를 저장하는 Edge 클래스

//신들의 좌표를 저장할 God 클래스
static class God{
    //x,y 좌표
    double x,y;
    //생성자
    public God(double x, double y) {
        this.x = x;
        this.y = y;
    }
    //다른 신과의 거리
    public double getLength(God god) {
        return Math.sqrt((Math.pow(this.x-god.x, 2)+Math.pow(this.y-god.y,2)));
    }
}
//크루스칼 알고리즘을 위한 edge(간선) 클래스
static class Edge implements Comparable<Edge>{
    int godNo1, godNo2;
    double length;

    public Edge(int godNo1, int godNo2, double length) {
        this.godNo1=godNo1;
        this.godNo2=godNo2;
        this.length=length;
    }
    //간선 길이에 대한 정리
    @Override
    public int compareTo(Edge o) {
        if(this.length<o.length) return -1;
        if(this.length>o.length) return 1;
        return 0;
    }

    @Override
    public String toString() {
        return godNo1+"~"+godNo2+" => "+length;
    }
}

 

크루스칼 알고리즘을 통해 최소 신장 트리의 길이 계산

//최소 신장 트리 알고리즘 => kruskal
public static double mst() {
    //간선의 수, M개만큼 이미 만들어져있음
    int edgeCount = M;
    //간선 합계
    double sum = 0d;
    //연결이 되지 않은 간선들을 추가
    for(int i=0;i<N;i++) {
        God god = Gods.get(i);
        for(int j=i+1;j<N;j++) {
            //부모가 같으면 패쓰
            if(isSameParent(i, j)) continue;
            //부모가 다르면 간선 정보 추가
            Edges.add(new Edge(i,j,god.getLength(Gods.get(j))));
        }
    }
    //간선 길이에 따라 정렬
    Collections.sort(Edges);
    //탐색
    for(Edge edge:Edges) {
        int god1 = edge.godNo1;
        int god2 = edge.godNo2;
        //부모가 다르면
        if(!isSameParent(god1, god2)) {
            //부모 설정
            setParent(god1, god2);
            //edge 수 증가
            edgeCount++;
            //거리 추가
            sum+=edge.length;
            //간선수가 N-1이면 종료
            if(edgeCount==N-1) break;
        }
    }
    //합계 출력
    return sum;

}

전체 코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;

public class Main {
	//https://www.acmicpc.net/problem/1774
	static int N,M;
	static List<God> Gods;
	static List<Edge> Edges;
	static int[] Parents;
	
	//부모 찾기
	public static int getParent(int node) {
		if(Parents[node]==node) return node;
		return Parents[node]=getParent(Parents[node]);
	}
	//부모 세팅, 작은 수가 부모
	public static void setParent(int node1, int node2) {
		int p1 = getParent(node1);
		int p2 = getParent(node2);
		if(p1<p2) Parents[p2]=p1;
		else Parents[p1]=p2;
	}
	//같은 부모 판별
	public static boolean isSameParent(int node1, int node2) {
		return getParent(node1)==getParent(node2);
	}
	//신들의 좌표를 저장할 God 클래스
	static class God{
		//x,y 좌표
		double x,y;
		//생성자
		public God(double x, double y) {
			this.x = x;
			this.y = y;
		}
		//다른 신과의 거리
		public double getLength(God god) {
			return Math.sqrt((Math.pow(this.x-god.x, 2)+Math.pow(this.y-god.y,2)));
		}
	}
	//크루스칼 알고리즘을 위한 edge(간선) 클래스
	static class Edge implements Comparable<Edge>{
		int godNo1, godNo2;
		double length;
		
		public Edge(int godNo1, int godNo2, double length) {
			this.godNo1=godNo1;
			this.godNo2=godNo2;
			this.length=length;
		}
		//간선 길이에 대한 정리
		@Override
		public int compareTo(Edge o) {
			if(this.length<o.length) return -1;
			if(this.length>o.length) return 1;
			return 0;
		}
		
		@Override
		public String toString() {
			return godNo1+"~"+godNo2+" => "+length;
		}
	}
	//최소 신장 트리 알고리즘 => kruskal
	public static double mst() {
		//간선의 수, M개만큼 이미 만들어져있음
		int edgeCount = M;
		//간선 합계
		double sum = 0d;
		//연결이 되지 않은 간선들을 추가
		for(int i=0;i<N;i++) {
			God god = Gods.get(i);
			for(int j=i+1;j<N;j++) {
				//부모가 같으면 패쓰
				if(isSameParent(i, j)) continue;
				//부모가 다르면 간선 정보 추가
				Edges.add(new Edge(i,j,god.getLength(Gods.get(j))));
			}
		}
		//간선 길이에 따라 정렬
		Collections.sort(Edges);
		//탐색
		for(Edge edge:Edges) {
			int god1 = edge.godNo1;
			int god2 = edge.godNo2;
			//부모가 다르면
			if(!isSameParent(god1, god2)) {
				//부모 설정
				setParent(god1, god2);
				//edge 수 증가
				edgeCount++;
				//거리 추가
				sum+=edge.length;
				//간선수가 N-1이면 종료
				if(edgeCount==N-1) break;
			}
		}
		//합계 출력
		return sum;
		
	}
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st=new StringTokenizer(br.readLine());
		N=Integer.parseInt(st.nextToken()); //우주신의 개수
		M=Integer.parseInt(st.nextToken()); //이미 연결된 통로 개수
		Gods=new ArrayList<>(); //신들 리스트
		Edges=new ArrayList<>(); //간선 리스트
		Parents = new int[N]; //부모
		//부모 초기화
		for(int i=0;i<N;i++) {
			Parents[i]=i;
		}
		//신 좌표 입력
		for(int i=0;i<N;i++) {
			st=new StringTokenizer(br.readLine());
			double x = Double.parseDouble(st.nextToken());
			double y = Double.parseDouble(st.nextToken());
			Gods.add(new God(x, y));
		}
		//연결 간선
		for(int i=0;i<M;i++) {
			st=new StringTokenizer(br.readLine());
			//1씩 감소하여 사용함
			int god1 = Integer.parseInt(st.nextToken())-1;
			int god2 = Integer.parseInt(st.nextToken())-1;
			//부모 설정
			setParent(god1, god2);
		}
		//소숫점 둘쨰자리까지 출력
		System.out.printf("%.2f",mst());
		
		br.close();
	}
}

 

반응형

댓글