문제 링크: https://www.acmicpc.net/problem/18427

 

18427번: 함께 블록 쌓기

첫째 줄에 자연수 N, M, H가 공백을 기준으로 구분되어 주어진다. (1 ≤ N ≤ 50, 1 ≤ M ≤ 10, 1 ≤ H ≤ 1,000) 둘째 줄부터 N개의 줄에 걸쳐서 각 학생이 가진 블록들의 높이가 공백을 기준으로 구

www.acmicpc.net

▶문제

1번부터 N번까지의 학생들은 각각 블록들을 가지고 있다. 학생마다 최대 M개의 블록을 가지고 있을 수 있으며, 한 명의 학생이 가지고 있는 모든 블록들의 높이는 서로 다르다. 이때 1번부터 N번까지의 학생들이 가진 블록을 차례대로 사용하여 바닥에서부터 쌓아 올려 하나의 탑을 만들고자 한다.

단, 어떤 학생의 블록은 사용하지 않아도 되며 한 학생당 최대 1개의 블록만을 사용할 수 있다.

1번부터 N번까지의 학생들이 가지고 있는 블록들에 대한 정보가 주어졌을 때, 높이가 정확히 H인 탑을 만들 수 있는 경우의 수를 계산하는 프로그램을 작성하시오.

예를 들어 N=3, M=3, H=5일 때, 각 학생마다 가지고 있는 블록들의 높이가 다음과 같다고 가정하자.

  • 1번 학생: 2, 3, 5
  • 2번 학생: 3, 5
  • 3번 학생: 1, 2, 3

이 때, 탑의 높이가 정확히 5가 되도록 블록을 쌓는 경우로는 다음의 6가지가 존재한다. (블록을 사용하지 않는 경우는 X로 표시하였다.)


▶입력

첫째 줄에 자연수 N, M, H가 공백을 기준으로 구분되어 주어진다. (1 ≤ N ≤ 50, 1 ≤ M ≤ 10, 1 ≤ H ≤ 1,000) 둘째 줄부터 N개의 줄에 걸쳐서 각 학생이 가진 블록들의 높이가 공백을 기준으로 구분되어 주어진다.

단, 모든 블록의 높이는 1,000 이하의 자연수이며 한 명의 학생이 가지고 있는 모든 블록들의 높이는 서로 다르게 주어진다.


▶출력

첫째 줄에 높이가 H인 탑을 만드는 경우의 수를 10,007로 나눈 나머지를 출력한다.


▶해설

DP 문제입니다. 조건을 살펴보겠습니다.

1. 한 명당 한 개의 블록만 쌓을 수 있다.

2. 블록을 쌓지 않아도 된다.

 

간단하게 풀 수 있습니다. 첫 번째 사람은 자신이 가진 블록만 사용하므로 만들 수 있는 것이 자신이 가진 것뿐입니다. 

(아무것도 쌓지 않는 경우도 1이라고 하겠습니다.)

예시를 보겠습니다. 

3 3 5

2 3 5

3 5 

1 2 3

 

첫 번째 사람

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




1



 

아무것도 쌓지 않은 경우의 수 0, 가진 블록 2, 3, 5로 한 가지의 경우의 수를 채웠습니다.

 

두 번째 사람 

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

3일 때

자신이 가진 같은 값의 블록의 수(1) + 전 사람이 3을 만든 경우의 수(1) = 2로 됩니다. 

5일 때 

자신이 가진 같은 값의 블록의 수(1) + 전 사람이 5를 만든 경우의수(1) + (자신이 가진 블록의 값 + 전 사람이 만든 블록의 경우의 수)(1) = 3입니다. 

 

세 번째 사람 

0 1 2 3 4 5
1 0 1 1 0 1
1 0 1 2 0 3
1 1 3 4 3 6

 

5일 때만 보겠습니다.

자신이 가진 같은 값의 블록의수 (0) + 전 사람이 5를 만든 경우의 수 (3) + (자신이 가진 블록의 값 + 전 사람이 만든 블록의 경우의 수)(3) = 6입니다.

 

즉 점화식은 아래와 같이 구성됩니다. 

for(int i=1; i<=n; i++){
    for(int j=1; j<=h; j++){ // h의 범위는 1~1000입니다. 
        for(Integer integer: list[i]){
            if(j>=integer){ // j>=integer이 의미하는 것은 내가 가진 블록값 + 이전까지 만든 블록의 값을 더해서 경우의 수를 만들 수 있다는 뜻입니다.  
                dp[i][j] += dp[i-1][j-integer];  // 즉 이전까지 만든 경우의 수를 더합니다. 
                dp[i][j] %=10007;
            }
        }
        dp[i][j] += dp[i-1][j]; // 이전까지 값이 만들어진 경우를 더합니다. 
        dp[i][j] %=10007;
    }
}

전체 코드 

import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader (new InputStreamReader (System.in));
        String[] s = br.readLine().split(" ");
        int n = Integer.parseInt(s[0]);
        int m = Integer.parseInt(s[1]);
        int h = Integer.parseInt(s[2]);
        int [][] dp = new int[n+1][1001];

        List<Integer>[] list = new ArrayList[n+1];

        for(int i=1; i<=n; i++){
            list[i] = new ArrayList<>();
            StringTokenizer st = new StringTokenizer(br.readLine());
            while(st.hasMoreTokens()){
                list[i].add(Integer.parseInt(st.nextToken()));
            }
        }

        for(int i=0; i<=n; i++){
            dp[i][0]=1;
        }

        for(int i=1; i<=n; i++){
            for(int j=1; j<=h; j++){
                for(Integer integer: list[i]){
                    if(j>=integer){
                        dp[i][j] += dp[i-1][j-integer];
                        dp[i][j] %=10007;
                    }
                }
                dp[i][j] += dp[i-1][j];
                dp[i][j] %=10007;
            }
        }
        System.out.println(dp[n][h]);
    }
}