거스름돈 계산하기
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);
}
}