Post

BOJ 1943 동전 분배

BOJ 1943 동전 분배

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

문제

윤화와 준희는 솔선수범하여 쓰레기를 줍는 착한 일을 하였다. 원장선생님께서는 윤화와 준희를 칭찬하시고 과자나 사 먹으라고 하시며 동전 몇 개를 윤화와 준희에게 건네 주었다.

그런데 돈을 받은 윤화와 준희는 좋아하기보다 고민에 빠지고 말았다. 원장선생님께 받은 이 돈을 어떻게 나누어 할지 고민에 빠진 것이다. 두 사람 모두 상대방이 자기보다 1원이라도 더 받는 것은 도저히 인정할 수 없어 한다. 따라서 돈을 똑같이 둘로 나누어 가져야 두 사람이 모두 만족할 수 있게 된다.

하지만 두 사람에게 돈을 똑같이 나누는 것이 불가능한 경우도 있다. 예를 들어 500원짜리 1개와 50원짜리 1개를 받았다면, 이 돈을 두 사람이 똑같이 나누어 가질 수는 없다. 물론 동전을 반으로 잘라서 나누어 가질 수도 있겠지만 그러면 돈으로서의 가치를 잃기 때문에 그렇게 할 수는 없다.

이제 우리가 할 일은 다음과 같다. 원장 선생님께서 N가지 종류의 동전을 각각 몇 개씩 주셨을 때, 그 돈을 반으로 나눌 수 있는지 없는지 판단하는 것이다.


입력

세 개의 입력이 주어진다. 각 입력의 첫째 줄에 동전의 종류 N(1 ≤ N ≤ 100)이 주어진다. 각 입력의 둘째 줄부터 N+1째 줄까지 각각의 동전의 금액과 개수가 빈 칸을 사이에 두고 주어진다. 단, 원장선생님께서 주신 금액의 총 합은 100,000원을 넘지 않는다. 동전의 금액과 개수는 자연수이고, 같은 금액을 가진 동전이 두 번 이상 주어지는 경우는 없다.


출력

첫째 줄부터 세 줄에 걸쳐, 각 입력에 대하여 반으로 나누는 것이 가능하면 1, 불가능하면 0을 출력한다.


예제 입력 1

2
500 1
50 1
3
100 2
50 1
10 5
3
1 1
2 1
3 1

예제 출력 1

0
1
1

코드

반례를 생각해내는 것도 문제 풀이에 있어 상당히 중요한 부분이기 때문에, 문제에서 주어진 예제도 문제 난이도에 영향을 미친다고 생각한다.

이번 문제를 읽고 예제를 봤을 때, 그리디로 풀면 바로 풀릴 것 같았지만, 이런 문제가 정말 조심해야 하는 문제인 것 같다.

문제의 예제만 보면 그리디로 풀어도 문제 없을 것 같지만, 5, 4, 3, 2, 2와 같은 경우는 그리디로 풀리지 않는다.

코딩 테스트는 시간도 중요하기 때문에, 문제를 처음 접할 때 예제만 보고 바로 풀이 방식을 결정한다면 큰 타격을 입을 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {

    static class Coin {
        int value;
        int quantity;

        public Coin(int value, int quantity) {
            this.value = value;
            this.quantity = quantity;
        }
    }

    static int N;
    static int halfOfAmount; // 원장 선생님께서 주신 총액의 절반
    static Coin[] coins;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder answers = new StringBuilder();
        int T = 3;
        while (T-- > 0) {
            N = Integer.parseInt(br.readLine());
            halfOfAmount = 0;
            coins = new Coin[N];

            for (int i = 0; i < N; i++) {
                StringTokenizer st = new StringTokenizer(br.readLine());
                int value = Integer.parseInt(st.nextToken());
                int quantity = Integer.parseInt(st.nextToken());
                coins[i] = new Coin(value, quantity);
                halfOfAmount += value * quantity;
            }
            // 원장 선생님이 주신 총액이 홀수일 경우, 무조건 불가능
            if((halfOfAmount & 1) != 0) {
                answers.append('0').append('\n');
                continue;
            }

            // 총액의 절반을 저장 
            halfOfAmount >>= 1;

            char answer = splitAmount();
            answers.append(answer).append('\n');
        }

        System.out.println(answers);
    }

    static char splitAmount() {
        // 특정 금액을 만들 수 있는지 DP로 판단하기 위한 배열로 인덱스는 금액을 의미한다.
        boolean[] dp = new boolean[halfOfAmount + 1];
        dp[0] = true; // 맨 처음 상태가 0원이기 때문

        for (Coin coin: coins) {
            // dp 배열을 맨 끝부터 맨 처음까지 순회하며 검사
            for (int i = halfOfAmount; i >= 0; i--) {
                // 지금까지 검사한 코인으로 금액 i를 만들 수 있을 경우
                // 금액 i에 검사 중인 동전을 한 개씩 추가하며 만들 수 있는 금액을 체크
                if(dp[i]) {
                    int madeAmount = i;
                    for (int j = 1; j <= coin.quantity; j++) {
                        madeAmount += coin.value;

                        if(madeAmount > halfOfAmount) {
                            break;
                        }

                        dp[madeAmount] = true;
                    }
                }
            }
        }
        
        if(dp[halfOfAmount]) {
            return '1';
        }

        return '0';
    }
}
This post is licensed under CC BY 4.0 by the author.