이 문제의 핵심은 "순서가 다른 구성을 같은 것으로 볼 것인가?" 입니다.
예를 들어 3원을 만들기 위해 1원, 2원을 쓴다면:
1원 + 2원2원 + 1원위 두 가지를 다르게 세면 순열, 하나로 본다면 조합입니다. 문제에서는 "동전의 구성을 따진다"고 했으므로, 순서만 다른 것은 중복으로 처리하여 한 가지 경우로 세어야 합니다. 이를 위해 반복문(Loop)의 순서를 전략적으로 설계해야 합니다.
가장 직관적인 방법은 DFS(백트래킹)지만, 이 최대 10,000이고 시간 제한이 있으므로 지수 시간 복잡도를 가지는 재귀는 적합하지 않습니다. 따라서 중복된 하위 문제(Overlapping Subproblems)를 해결하는 DP를 사용합니다.
중복을 방지하기 위한 루프 설계의 핵심은 다음과 같습니다:
금액을 기준으로 먼저 돌고, 내부에서 동전을 돌리면 1+2와 2+1이 중복 카운팅됩니다.동전을 기준으로 먼저 돌고, 내부에서 금액을 채워나가면, "1원만 썼을 때", "1원과 2원을 썼을 때" 처럼 동전이 추가되는 흐름이 강제되어 중복이 자연스럽게 제거됩니다.를 금액 를 만드는 경우의 수라고 정의합니다.
어떤 동전의 가치가 라고 할 때, 금액 를 만드는 방법은 "이미 원을 만든 경우의 수에 원 동전 하나를 얹는 것"과 같습니다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
class Main {
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StringTokenizer st;
static int T, N, M;
static int[] coin;
public static void main(String[] args) throws IOException {
StringBuilder sb = new StringBuilder();
T = Integer.parseInt(br.readLine()); // 테스트 케이스 개수
for (int i = 0; i < T; i++) {
N = Integer.parseInt(br.readLine()); // 동전의 가지 수
coin = new int[N];
st = new StringTokenizer(br.readLine());
for (int j = 0; j < N; j++)
coin[j] = Integer.parseInt(st.nextToken());
M = Integer.parseInt(br.readLine()); // 목표 금액
int answer = solv();
sb.append(answer).append('\n');
}
System.out.println(sb);
}
private static int solv() {
// DP[i]: 금액 i를 만드는 경우의 수
int[] dp = new int[M + 1];
// 초기값 설정: 0원을 만드는 방법은 1가지 (아무 동전도 선택하지 않음)
dp[0] = 1;
// Outer Loop: 동전 (중복 방지 핵심)
for (int c : coin) {
// Inner Loop: 금액
for (int i = c; i <= M; i++) {
dp[i] += dp[i - c];
}
}
return dp[M];
}
}
처음 접근할 때 "구성(Combination)"이 아닌 "순서(Permutation)"까지 포함하여 카운팅하는 실수를 범하기 쉽습니다.
for(i: 1~M) 안에 for(c: coin)을 넣었다면:{1, 2}, {2, 1}을 별개로 칩니다.for(c: coin)을 바깥쪽 루프로 배치하여, "현재 동전 c를 사용할지 말지"를 결정하며 테이블을 갱신했습니다. 이렇게 하면 작은 동전부터 차례대로 사용한 경우만 누적되므로 순서가 강제됩니다.