코딩테스트/백준

[백준 1033] 칵테일 (Java) - DFS, 최대공약수, 최소공배수

imachill7guy 2025. 3. 11. 17:54

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

칵테일

문제

august14는 세상에서 가장 맛있는 칵테일이다. 이 칵테일을 만드는 정확한 방법은 아직 세상에 공개되지 않았지만, 들어가는 재료 N개는 공개되어 있다. 

경근이는 인터넷 검색을 통해서 재료 쌍 N-1개의 비율을 알아냈고, 이 비율을 이용해서 칵테일에 들어가는 전체 재료의 비율을 알아낼 수 있다.

총 재료 쌍 N-1개의 비율이 입력으로 주어진다. 이때, 칵테일을 만드는데 필요한 각 재료의 양을 구하는 프로그램을 작성하시오. 이때, 필요한 재료의 질량을 모두 더한 값이 최소가 되어야 한다. 칵테일을 만드는 재료의 양은 정수이고, 총 질량은 0보다 커야한다.

비율은 "a b p q"와 같은 형식이고, a번 재료의 질량을 b번 재료의 질량으로 나눈 값이 p/q라는 뜻이다.

입력

첫째 줄에 august14를 만드는데 필요한 재료의 개수 N이 주어지며, N은 10보다 작거나 같은 자연수이다.

둘째 줄부터 N-1개의 줄에는 재료 쌍의 비율이 한 줄에 하나씩 주어지는데, 문제 설명에 나온 형식인 "a b p q"로 주어진다. 재료는 0번부터 N-1까지이며, a와 b는 모두 N-1보다 작거나 같은 음이 아닌 정수이다. p와 q는 9보다 작거나 같은 자연수이다.

출력

첫째 줄에 칵테일을 만드는데 필요한 각 재료의 질량을 0번 재료부터 순서대로 공백으로 구분해 출력한다.

예제 입력 1 복사

5
4 0 1 1
4 1 3 1
4 2 5 1
4 3 7 1

예제 출력 1 복사

105 35 21 15 105

 


풀이

import java.io.*;
import java.util.ArrayList;
import java.util.StringTokenizer;

public class Main {
    static int N;
    static ArrayList<ingredient>[] A;
    static long lcm; // 최소 공배수
    static boolean[] visited; // 방문배열
    static long[] ratio;

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

        N = Integer.parseInt(br.readLine()); // 재료개수
        A = new ArrayList[N]; // 재료를 인접리스트로 생각한다.
        visited = new boolean[N]; // 방문배열 초기화
        ratio = new long[N]; // 재료 비율 값
        lcm = 1; // 최소공배수 초기화

        for(int i=0; i<N; i++){
            A[i] = new ArrayList<ingredient>();
        }

        for(int i=0; i<N-1; i++){
            StringTokenizer st = new StringTokenizer(br.readLine());
            int node1 = Integer.parseInt(st.nextToken());
            int node2 = Integer.parseInt(st.nextToken());
            long p = Long.parseLong(st.nextToken());
            long q = Long.parseLong(st.nextToken());

            // 인접노드 리스트 값 서로 넣어주기 (다른 재료와 비율)
            A[node1].add(new ingredient(node2, p, q));
            A[node2].add(new ingredient(node1, q, p));

            // 입력값 최소공배수 구하기
            lcm *= (p * q) / gcd(p,q);
        }

        ratio[0] = lcm;
        DFS(0);

        long ratioGCD = ratio[0];

        // 전체 DFS 탐색 후 배열 값들의 최대공약수로 나눠줘야됨
        // 배열 값들의 최대공약수 구하기
        for(int i=1; i<N; i++){
            ratioGCD = gcd(ratioGCD, ratio[i]);
        }

        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        for(int i=0; i<N; i++){
            bw.write(ratio[i]/ratioGCD + " ");
        }

        bw.flush();
        bw.close();

    }
    // 최대공약수 구하기
    private static long gcd(long A, long B){
        // 만약 B가 0으로 들어왔다면 최대공약수 구했으므로 return
        if(B == 0) return A;
        // 그게 아니라면 다시 나머지 연산
        return gcd(B, A%B);
    }

    // DFS 수행 (i번째 node DFS 수행)
    private static void DFS(int i){
        // 방문하지 않았다면 방문체크 후 인접리스트 dfs 수행
        visited[i] = true;

        for(ingredient next : A[i]){
            // 인접리스트 순회하면서 방문하지 않은 노드 있다면
            int b = next.getB();
            if(!visited[b]){
                // 비율계산해서 값 update 후 DFS 재귀 호출
                // 나눗셈 먼저하면 0이 나올 수 있으므로 곱셈먼저 수행
                ratio[b] = ratio[i] * next.getQ() / next.getP();
                DFS(b);
            }
        }
    }
}

// ingredient 클래스 정의
class ingredient{
    int b; // 다른 재료 번호
    long p; // 비율 p
    long q; // 비율 q

    public ingredient(int b, long p, long q){
        super();
        this.b = b;
        this.p = p;
        this.q = q;
    }

    public int getB(){
        return b;
    }

    public long getP() {
        return p;
    }

    public long getQ(){
        return q;
    }
}

 

  • 구현하는게 레전더리 어려움;;
  • 각 재료를 그래프라고 생각한다. -> 인접 리스트 구현
    • 2번째 줄 ~ N-1번째 줄의 값을 받으면서 최대공약수를 구하여 lcm(최소공배수)에 곱해주면서 업데이트한다.
    • 2번째줄 부터는 입력 값의 형태가 a b p q 라면
    • a/b = p/q이다.
    • 이러한 구조를 저장하기 위해 새로운 클래스를 정의한다.(class ingredient)
    • 인접노드 리스트에 각각 서로의 인접노드 번호값, p, q(혹은 q, p)값을 넣어준다.
  • 임의의 노드에서 DFS 문을 수행한다.
    • 비율값을 저장하는 배열의 값은 모두 0으로 초기화 되어있으므로 계산해두었던 lcm을
    • 시작하고자하는 임의의 노드번호에 저장해주고 해당 번호부터(0번) DFS를 수행한다.

DFS

  • 방문 체크 후 해당 번호의 노드의 인접리스트 순회한다.
  • 인접리스트를 순회하면서 방문하지 않은 노드를 발견한다면
    • 해당 노드번호의 ratio배열 값을 계산 후 update 해준다
    • a/b = p/q 이므로
    • b = a * q / a 이다. 이 값을 ratio[b]값에 업데이트 해주면된다.
    • 그리고 DFS(b)를 수행한다.

 

DFS를 모두 수행한 후

  • DFS를 모두 수행했다는 뜻은 모든 노드를 방문하고 ratio 배열값이 채워져있다는 뜻이다.
  • 하지만 필요한 재료의질량은 모두 더한 값이 최소가 되어야 하므로, 즉 최소 공배수를 만족해야 한다는 뜻이다.
  • 배열값의 최대공약수를 구해준 뒤, 배열의 값을 출력할때 최대공약수로 나눠주면서 출력한다.