Algorithm
코드트리
거스름돈 계산하기

거스름돈 계산하기

https://www.codetree.ai/problems/calculating-change/description (opens in a new tab)

내 풀이

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
 
public class Main {
    /**
     * 동전의 가치
     */
    static int N;
    /**
     * 거스름돈의 액수 S
     */
    static int S;
 
    static int DEFAULT_CNT;
 
    static int MIN = -1;
 
    static int[][] MAP;
 
    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());
        S = Integer.parseInt(st.nextToken());
        MAP = new int[N][2];
 
        int cnt = 0;
        for (int i = 0; i < N; i++) {
            st = new StringTokenizer(br.readLine());
            int V = Integer.parseInt(st.nextToken());
            int A = Integer.parseInt(st.nextToken());
            cnt += A;
            S -= V * A;
            MAP[i][0] = V;
            MAP[i][1] = A;
        }
        DEFAULT_CNT = cnt;
 
        dfs(N - 1, S, 0);
 
        if (MIN != -1) {
            System.out.println(MIN + DEFAULT_CNT);
        } else {
            System.out.println(-1);
        }
    }
 
    static void dfs(int idx, int point, int cnt) {
        // 이미 MIN을 넘어섰다면 더 탐색 X
        if ((MIN != -1 && cnt > MIN) || idx == -1) {
            return;
        }
        int curV = MAP[idx][0]; // 현재 동전의 가치
        if (point == 0) {
            MIN = minValue(cnt);
        }
 
        int maxMultiple = 1;
        if (curV != 0) {
            maxMultiple = point / curV == 0 ? 1 : point / curV;
        }
 
        for (int i = 0; i <= maxMultiple; i++) {
            int newCnt = cnt + i;
            int newPoint = point - curV * i;
            dfs(idx - 1, newPoint, newCnt);
        }
    }
 
    static int minValue(int newV) {
        if (MIN != -1 && newV != -1) {
            return Math.min(MIN, newV);
        }
        return Math.max(MIN, newV);
    }
}

해설

DP를 사용하는 과정이 이해가 잘 가지 않아서 이런 저런 풀이를 많이 덧붙여봤지만 핵심이 되는 부분은 DP로 경우의 수를 구하고 INF와 비교해서 INF와 동일하다면 -1을 출력하고 그렇지 않다면 dp[s] + bef를 출력하는 것이다.

import java.util.Scanner;
 
public class Main {
    public static final int INF = 1012345678;
 
    public static final int MAXN = 1005;
    public static final int MAXS = 100005;
 
    public static int n; // 동전의 가지 수
    public static int s; // 거스름 돈의 액수
    public static int[] v = new int[MAXN];   // 동전의 가치
    public static int[] a = new int[MAXN];   // 동전의 최소 개수
 
    // dp[i] : i 가치를 만들기 위한 최소 동전의 개수
    public static int[] dp = new int[MAXS];
    public static int bef;       // 동전들의 초기 개수의 합
 
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
 
        // 변수를 입력받습니다.
        n = sc.nextInt();
        s = sc.nextInt();
 
        // 입력 값을 받으면서 최소로 사용하는 동전의 개수만큼 목표 가치를 계산해둡니다.
        for(int i = 1; i <= n; i++) {
            v[i] = sc.nextInt(); // i번째 동전의 가치
            a[i] = sc.nextInt(); // i번째 동전의 최소 개수
            s -= v[i] * a[i];
            bef += a[i];
        }
 
        // 최소를 구해야 하므로 초기값으로 매우 큰 숫자를 넣어줍니다.
        for(int i = 1; i <= s; i++) dp[i] = INF;
 
        /**
         * s - v[i]는 목표 값 - 현재 밸류를 의미
         * j + v[i]는 다음 값 + 현재 값을 의미 현재 값이 3이라면 4, 5, 6, 7 ...
         * j <= s - v[i]가 두 번째 for 문의 조건이 되는 이유는 동전의 가치 v[i]를 현재 가치 j에 더하여 새로운 가치 j + v[i]를 만들 때, 이 값이 목표 가치 s를 넘지 않도록 하기 위해서 임
         *
         * v[i]가 한 번만 사용 되는건 아닌지에 대한 답변
         * 	•	반복적인 갱신: 두 번째 for 문에서 j가 0부터 s - v[i]까지 모든 가능한 가치를 순회합니다. 이때, j + v[i] 값을 계속 갱신하게 됩니다.
         * 	•	누적 사용: dp[j] + 1은 현재 가치 j를 만드는 최소 동전 개수에 v[i] 동전을 한 번 더 추가하는 것을 의미합니다. 이 갱신이 반복적으로 이루어지면서 v[i]를 여러 번 사용할 수 있게 됩니다.
         */
        for(int i = 1; i <= n; i++) { // i는 사용 가능한 동전의 종류를 의미
            for(int j = 0; j <= s - v[i]; j++) { // j는 현재 목표 가치에 도달하기 위한 중간 단계의 가치
                dp[j + v[i]] = Math.min(dp[j + v[i]], dp[j] + 1);
            }
        }
 
        // 결과를 출력합니다.
        // 만약 목표 가치를 만들 수 없다면 -1을, 만들 수 있다면 필요한 최소 개수와 초기 개수의 합을 출력합니다.
        if(dp[s] == INF) System.out.println(-1);
        else System.out.println(dp[s] + bef);
    }
}