코딩테스트/백준_Java

Q. 16566 자바 : 카드 게임

Ski_ 2025. 5. 18. 22:55

Q. 16566 자바 : 카드 게임

 

문제 : boj.kr/16566

P5 난이도의 문제이다.

 

문제를 간단히 설명하자면

카드 더미가 주어진다. 이 때 상대는 나와 동일한 카드 더미를 가진다.

그리고 상대의 카드가 주어질 때 상대의 카드보다 크면서 이전에 낸 적 없는 가장 작은 카드를 출력하는 문제이다.


먼저 완전 탐색 방식으로 푼다고 가정하면 아래와 같이 풀었을 것 같다.

1. 상대의 카드보다 크면서 이전에 낸 적 없는 카드 중 가장 작은 카드를 카드 더미에서 찾는다.

2. 사용한 카드는 사용했다고 처리한다.

3. 1~2 과정을 반복한다.

 

이렇게 푼다면 낼 수 있는 카드를 찾을 때 N^2번의 연산이 생기고,

이전에 낸 적 없는 카드를 찾는데 최악의 경우 N번의 연산이 생기므로 총 N^3 * K의 시간복잡도를 가지게 된다.

시간제한은 1.2초이므로 이 방법은 불가능해 보였다.

 

K는 어쩔 수 없지만, N의 경우 N log N 정도로 시간복잡도를 줄여야 한다.


이 문제에서 중요한 점은 두 가지이다.

1. 상대방의 카드보다 크면서 가장 작은 카드를 카드 더미에서 찾는다. - 이분 탐색 O(N log N)

2. 이전에 사용했던 카드는 사용할 수 없다. - untion-find O(1)

 

1번 과정은 이분 탐색을 사용한다면 N log N의 시간 복잡도로 탐색 시간을 줄일 수 있다.

 

2번 과정도 N번의 연산이 필요하고, 이를 줄이기 위한 아이디어를 떠올리는데 어려웠다.

핵심 아이디어는 n번 카드가 이미 사용됬다면, 그 다음에 사용할 수 있는 카드는 n+1번 카드라는 점 이다.

union-find를 사용한다면 이 연산을 상수 시간으로 줄일 수 있다.


처음에는 모든 카드가 사용되지 않았으므로 부모를 나 자신으로 갖는다. - parent[i] = i

1번 과정으로 카드를 사용했다면 사용한 카드의 부모는 내 다음 카드로 대입한다. - union(n, n+1)

이 때 n + 1이 전체 카드 숫자의 범위를 초과할 수 있으므로 예외 처리가 필요하다.

 

2번의 연산 속도는 상수이므로 전체 시간 복잡도를 O(K*N logN)으로 줄일 수 있다.

 

코드는 아래와 같다.

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

class Main {

    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static StringBuffer sb = new StringBuffer();
    static StringTokenizer st;

    static int n, m, k, curSubmit;
    static int[] cards, submits, parents;

    static void input() throws IOException{
        st = new StringTokenizer(br.readLine());

        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
        k = Integer.parseInt(st.nextToken());

        cards = new int[m];
        st = new StringTokenizer(br.readLine());
        for (int i = 0; i < m; i++) {
            cards[i] = Integer.parseInt(st.nextToken());
        }

        submits = new int[k];
        st = new StringTokenizer(br.readLine());
        for (int i = 0; i < k; i++) {
            submits[i] = Integer.parseInt(st.nextToken());
        }
    }

    static void pro() {

        Arrays.sort(cards);
        parents = new int[m];
        for (int i = 0; i < m; i++) parents[i] = i;

        for (int i = 0; i < k; i++) {
            curSubmit = submits[i];
            int result = 0;

            int left = 0, right = m - 1;
            while (left <= right) {
                int mid = (left + right) / 2;
                int available = find(mid);

                if (determination(cards[available])) {
                    result = available;
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            }

            sb.append(cards[result]).append('\n');
            if (result + 1 != m) union(result + 1, result);
        }
        System.out.println(sb);
    }

    static int find(int p) {
        if (parents[p] == p) return p;
        return parents[p] = find(parents[p]);
    }
    
    static void union(int a, int b) {
        int aParent = find(a);
        int bParent = find(b);

        parents[bParent] = aParent;
    }

    static boolean determination(int element) {
        return curSubmit < element;
    }

    public static void main(String[] args) throws Exception{
        input();
        pro();
    }
}

 

반응형