PS/백준

[백준][Java] 16987번 : 계란으로 계란치기

nyrimmm 2025. 6. 18. 00:48

 

[문제 링크]

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


문제 설명

각 계란은 내구도와 무게를 가지고 있다.
계란으로 계란을 치면, 각 계란의 내구도는 상대 계란의 무게만큼 깎인다.

내구도가 0 이하면 계란이 깨진다.

ex) 계란 1의 내구도 7 무게 5, 계란 2의 내구도 3 무게 4

두 개란을 치면, 계란 1의 내구도는 7-4=3, 계란 2의 내구도는 3-5=-2 으로 계란 2가 깨진다.

 

일렬로 놓인 계란을 왼쪽부터 차례로 들어서 아래와 같은 규칙으로 한번씩만 쳐서 계란을 깨뜨린다.

1. 가장 왼쪽 계란을 든다.

2. 현재 들고있는 계란으로 깨지지 않은 다른 계란을 하나 친다.

    이 때, 손에 든 계란이 깨졌거나 깨지지 않은 다른 계란이 없으면 넘어간다.

3. 현재 들고있는 계란을 내려놓고, 가장 최근에 든 계란의 바로 오른쪽 계란을 손에 들고 2번 과정을 다시 진행한다.

    이 때, 가장 최근에 든 계란이 가장 오른쪽일 경우, 계란치기를 종료한다.

 

최대한 많이 깨뜨릴 수 있는 계란의 개수를 구한다.

 

 

 

 

 

 

문제 풀이

나의 풀이

  • 제일 왼쪽 계란부터 순서대로 모든 경우를 탐색하는 방식
  • 1차원 배열 broken을 통해서 해당 계란이 깨졌는지 안깨졌는지를 표시한다.
  • 손에 든 계란이 깨졌거나, 깨지지 않은 다른 계란이 없을 경우 현재  손에 든 계란을 놓고 오른쪽 계란을 들어서 계란치기 과정을 수행한다. → eggBreak(hand + 1, cnt)
  • 손에 든 계란과 다른 계란을 쳐서
    • 각 계란의 내구도를 갱신
    • 두 계란이 깨졌는지 확인해서 cnt 증가, broken 배열 갱신
    • 재귀 호출로 현재 손에 든 계란의 오른쪽에 위치한 계란으로 이동 후 과정 수행
    • 재귀 호출이 끝나면, 내구도, cnt, broken 배열 원상복구

 

전체코드

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

public class Main {

    static int n, max = 0;
    static int[][] eggs;
    static boolean[] broken = new boolean[8];

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;

        n = Integer.parseInt(br.readLine());    //계란의 수

        eggs = new int[n][2];
        //계란의 내구도와 무게 입력받기(왼 -> 오)
        for (int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            eggs[i][0] = Integer.parseInt(st.nextToken());  //내구도
            eggs[i][1] = Integer.parseInt(st.nextToken());  //무게
        }

        eggBreak(0, 0);
        System.out.println(max);
    }


    /**
     * @param hand 손에 들고 있는 계란의 인덱스
     * @param cnt  깬 계란의 개수
     */
    public static void eggBreak(int hand, int cnt) {
        //종료 - 가장 최근의 든 계란이 가장 오른쪽 계란일 경우
        if (hand == n) {
            max = Math.max(max, cnt);
            return;
        }

        //손에 든 계란이 깨졌거나, 깨지지 않은 다른 계란이 없는 경우
        if (broken[hand] || cnt == n - 1) {
            eggBreak(hand + 1, cnt);
            return;
        }

        int temp = cnt;
        for (int i = 0; i < n; i++) {
            if (hand == i) {
                continue;
            }

			//깨지지 않은 다른 계란일 경우 계란 치기 실행
            if (!broken[i]) {
                hitEgg(hand, i);    //두 계란을 치기
                if (eggs[hand][0] <= 0) {
                    cnt++;
                    broken[hand] = true;
                }
                if (eggs[i][0] <= 0) {
                    cnt++;
                    broken[i] = true;
                }
                eggBreak(hand + 1, cnt);
                restoreEgg(hand, i);//원래대로
                cnt = temp;
            }
        }
    }


    //두 계란 치기
    private static void hitEgg(int hand, int i) {
        //계란의 내구도 -= 상대 계란의 무게
        eggs[hand][0] -= eggs[i][1];
        eggs[i][0] -= eggs[hand][1];
    }

    private static void restoreEgg(int hand, int i) {
        eggs[hand][0] += eggs[i][1];
        eggs[i][0] += eggs[hand][1];
        broken[hand] = false;
        broken[i] = false;
    }
}

 

 

 

 

 

 

 

다른 풀이

1. broken 배열

위의 풀이에서 보면 계란의 깨짐여부를 나타내는 broken 배열이 있는데, 생각해보면 이는 굳이 필요하지 않다.

eggs 배열에서 계란의 내구도가 0 이하인 경우 계란이 깨진 상태이다. 그렇기 때문에 (계란의 내구도) <= 0 으로 계란의 깨짐여부를 를 판단할 수 있다.

 

2. temp 변수

for문을 시작하기 전에 현재 cnt의 값을 temp에 임시로 저장해두었다. 이는 재귀가 끝나고서 cnt값을 원상복구할 때 사용하기 위해서이다.

하지만 아래와 같이 재귀호출 시에 삼항연산자로 각 계란이 깨졌는지 안깨졌는지를 따져서 cnt를 증가시키면 temp는 없어도 된다.(계란이 깨진 경우 1, 깨지지 않은 경우 0)

 

3. flag 변수

위의 풀이에서 보면 '깨지지 않은 다른 계란이 없을 경우' 손에 든 계란을 놓고 오른쪽 계란을 들어서 계란치기 과정을 수행하도록 했다. → eggBreak(hand + 1, cnt)

여기에서는 flag 라는 변수를 사용해서 현재 손에 든 계란으로 계란치기를 한 번이라도 수행했는지를 표시한다.

수행한 경우 true, 수행하지 못한 경우 false

계란치기를 한번이라도 수행하지 못한 경우 → 깨지지 않은 다른 계란이 없다는 의미 이다.

eggBreak(hand + 1, cnt + (eggs[hand][0] <= 0 ? 1 : 0) + (eggs[i][0] <= 0 ? 1 : 0));
더보기

다른 풀이 코드

public static void eggBreak(int hand, int cnt) {
    //종료 - 가장 최근의 든 계란이 가장 오른쪽 계란일 경우
    if (hand == n) {
        max = Math.max(max, cnt);
        return;
    }

    if (eggs[hand][0] <= 0) {   //손에 든 계란이 깨진 경우 -> 내려놓고 다음 계란을 든다
        eggBreak(hand + 1, cnt);
    } else {                    //손에 든 계란이 깨지지 않은 경우 -> 계란치기 실행
        boolean flag = false;   //계란치기를 한번이라도 했는지 안했는지
        for (int i = 0; i < n; i++) {
            if (hand == i || eggs[i][0] <= 0) { //
                continue;
            }
            flag = true;
            eggs[hand][0] -= eggs[i][1];
            eggs[i][0] -= eggs[hand][1];
            eggBreak(hand + 1, cnt + (eggs[hand][0] <= 0 ? 1 : 0) + (eggs[i][0] <= 0 ? 1 : 0));
            eggs[hand][0] += eggs[i][1];
            eggs[i][0] += eggs[hand][1];
        }

        if (!flag) {    //깨지지 않은 계란이 없어서 한 번도 치지 못한 경우
            eggBreak(hand + 1, cnt);
        }
    }
}

[참고]

https://www.youtube.com/watch?v=QgG1hIkrZME