알고리즘/DP

합-분해

히포파타마스 2022. 4. 29. 15:35

합-분해

백준 2225번 문제

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

 

2225번: 합분해

첫째 줄에 답을 1,000,000,000으로 나눈 나머지를 출력한다.

www.acmicpc.net

 

 

 

 

1. 문제

문제

0부터 N까지의 정수 K개를 더해서 그 합이 N이 되는 경우의 수를 구하는 프로그램을 작성하시오.

덧셈의 순서가 바뀐 경우는 다른 경우로 센다(1+2와 2+1은 서로 다른 경우). 또한 한 개의 수를 여러 번 쓸 수도 있다.

 

입력

첫째 줄에 두 정수 N(1 ≤ N ≤ 200), K(1 ≤ K ≤ 200)가 주어진다.

 

출력

첫째 줄에 답을 1,000,000,000으로 나눈 나머지를 출력한다.

 

예제 입력 1

20 2

예제 출력 1

21

 

 

예제 입력 2

6 4

예제 출력 2 

84

 

 

 

 

2. 풀이

■풀이 방법

0부터 N까지의 정수 K개를 더해서 그 합이 N이 되는 경우의 수를 구하라..

문제를 처음 봤을 때 잘 이해가 되지 않았다.

이럴 때는 문제의 주어진 조건의 경곗값을 넣어서 직접 답을 구해보는 것이 좋다.

 

문제에서 조건은 (1 ≤ N ≤ 200), (1 ≤ K ≤ 200) 이므로 N과 K에 각각 1을 넣어본다.

 

그러면 우리가 구해야 할 답은 다음과 같다.

 

"0부터 1까지의 정수 1개를 더해서 그 합이 1이 되는 경우의 수"

 

정수 한 개(K = 1)로 1(N = 1)을 만드는 경우는 "1" 그 자체를 넣는 수 한 가지밖에 없다.

 

이제 N을 조금씩 늘려보자.

K = 1, N = 2이라면?

정수 한 개로 2를 만드는 경우도 "2" 하나밖에 없다.

 

즉, K = 1일 때 N = n일 경우 그 경우는 "n" 단 하나이다.

 

이제 K = 2일 때를 생각해보자.

K = 2, N = 1이라면?

 

정수 두 개로 1을 만드는 경우는 "0 + 1", "1 + 0" 두 가지이다.

 

K = 2, N = 2이라면?

첫 번째 자리에 올 수 있는 수는 "0", "1", "2"로 총 3가지이다.

따라서 정수 두 개로 2를 만드는 경우는 "0 + 2", "1 + 1", "2 + 0"으로 세 가지이다. 

 

그런데 위의 경우를 뒤에서부터, 즉 두 번째 자리에서부터 생각해보자.

두번째 자리에 올 수 있는 수도 "0", "1", "2"로 총 3가지이다.

 

만약 두 번째 자리가 "1" 이 된다고 하자.

그러면 첫 번째 자리에 올 수 있는 값은 (2 - 1)로 "1"이 된다.

그런데 이것은 맨 처음에 했던 정수 한 개로 1을 만드는 경우와 같다.

 

즉 K = 2, N = 2일 경우의 답은 다음의 합과 같다.

정수 한개로 0을 만드는 경우 + 정수 한개로 1을 만드는 경우 + 정수 한개로 2를 만드는 경우

 

d[k][n]을 0부터 n까지의 정수 k개를 더해서 n을 만드는 경우의 수라고 한다면 다음과 같은 식이 성립한다.

 

[점화식]

d[k][n] = d[k - 1][0] + d[k - 1][1] + ... + d[k - 1][n]

 

K = 3, N = n일 때도 생각해보자.

세 번째 자리에 올 수 있는 수는 0부터 n까지이다.

세번째 자리에 0이 온다면

"? + ? + 0 = n"이 되는 경우의 수를 구해야 한다.

이는 정수 두 개로 n을 만드는 경우(d[2][n])와 같다.

 

세 번째 자리에 1이 온다면

"? + ? + 1 = n"이 되는 경우의 수를 구해야 한다.

이는 정수 두 개로 n - 1을 만드는 경우(d[2][n - 1])와 같다.

 

이런 식으로 세 번째 자리에 n이 온다면 이 경우의 수는 d[2][0]과 같을 것이다.

 

따라서 우리가 구하려는 0부터 n까지의 정수 세 개로 n을 만드는 방법의 수(d[3][n])는

d[2][0] + d[2][1] + ... + d[2][n] 으로 위의 점화식을 따른다.

 

또한 위의 점화식은 다음과 같이 바꿀 수 있다.

 

[점화식2]

d[k][n] = d[k][n - 1] + d[k - 1][n]
//d[k][n - 1] = d[k - 1][0] + d[k - 1][1] + ... + d[k - 1][n - 1] 이기 때문

위의 점화식으로 d[k][n]을 구하면 된다.

 

 

 

■ 코드

□ 입력

[입력]

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    int n = scanner.nextInt();
    int k = scanner.nextInt();
   
    .
    .
    .
   
}

입력받은 N과 K값으로 n과 k를 초기화한다.

 

 

□ 초기화

[초기화]

public static void main(String[] args) {
	
    .
    .
    .
    
    int[][] d = new int[k + 1][n + 1];   //[1]

    Arrays.fill(d[1], 1);   //[2]
    for (int i = 1; i < k + 1; i++) {   //[3]
        d[i][0] = 1;
    }
    
    .
    .
    .
   
}

● [1] : 입력받은 N과 K의 값으로 0부터 N까지의 정수 K개로 N을 만드는 경우의 수를 원소로 하는 배열 d를 생성한다.

           d[n][k]가 실제 의미하는 값을 n과 k에 맞추기 위해 각 값에 1을 더해서 초기화해준다.

           ex) d[n][k] = 0부터 n까지의 정수 k개로 n을 만드는 경우의 수

 

● [2] : 점화식 상 k = 0일 때와 n = 0일 때는 구할 수 없기 때문에 초기값을 설정해주어야 한다.

             그런데 k = 0은 정수 0개로 합을 만드는 경우라는 의미로 이 경우를 1로 주면 안 되고 0으로 주면 의미가 없기 때문에 k=0일 경우

             는 넘어가고 k = 1일 경우를 구해서 넣어준다.

             앞에서 직접 해본 것처럼 k = 1일 때의 경우는 n이 어떤 정수든 1이다.

 

● [3] : n = 0은 0을 만드는 경우의 수로 이 경우는 자릿수가 몇 개 던 0만을 더해야 하기 때문에 항상 1이다.

 

 

□ 계산, 출력

[계산, 출력]

public static void main(String[] args) {
        
        .
        .
        .

        for (int i = 2; i < k + 1; i++) {   //[1]
            for (int j = 1; j < n + 1; j++) {
                d[i][j] = d[i][j - 1] + d[i - 1][j];
                d[i][j] %= 1000000000;   //[2]
            }
        }

        System.out.println(d[k][n] % 1000000000);
    }
}

● [1] : 점화식을 사용해서 모든 d[][] 값을 구한다.

● [2] : 문제에서 답을 1,000,000,000으로 나눈 나머지 값을 요구하기 때문에 구해진 각 값을 1,000,000,000으로 나눈 나머지 값으로 변경해준다.

 

 

 

■ 전체 코드

[전체 코드]

public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int k = scanner.nextInt();
        int[][] d = new int[k + 1][n + 1];

        Arrays.fill(d[1], 1);
        for (int i = 1; i < k + 1; i++) {
            d[i][0] = 1;
        }

        for (int i = 2; i < k + 1; i++) {
            for (int j = 1; j < n + 1; j++) {
                d[i][j] = d[i][j - 1] + d[i - 1][j];
                d[i][j] %= 1000000000;
            }
        }

        System.out.println(d[k][n] % 1000000000);
    }
}