baekjoon/BinarySearch
백준 16566번 : 카드 게임 [자바]
Meluu_
2025. 6. 23. 09:52
🧫 문제 분석
✔️ 출처
📖 문제

이분탐색 + 유니온 파인드 (서로소 집합)문제
문제의 핵심은
철수는 카드를 조작해서 낼 수 있고, 같은 수여러개를 낼 수 도 있음
민수는 철수의 카드를 미리 알고 철수가 낸 카드 중 더 큰 수에서 가장 작은 수를 낸다.
문제에서 민수가 카드를 내지 못하는 경우가 없다고 가정하였으므로
철수가 낸 카드보다 작거나 같은 카드만 남을 경우가 없다는 의미이다.
즉, 철수보다 항상 더 큰 카드 k 개 만큼 내어 항상 승리한다
철수가 낸 카드에 맞게 민수는 자신의 카드에서 적절한 카드를 이분탐색으로 찾아서 낸다.
찾은 카드가 철수가 낸 카드와 같다면 그 다음 카드를 가리킨다.
사용한 카드는 그 다음 카드의 대표 집합에 연결한다.
다음 카드의 대표 집합을 연결하는 방법은 서로소 집합을 이용한다.
다음 카드도 사용했을 수 있으므로 사용가능한 다음카드의 대표집합(부모) 을 찾는다. : find()
현재 카드를 사용한 뒤에는 다음 카드로 연결한다 : union()
M과 N이 400만개이므로
Arrays.sort()로 정렬시 최악의 경우 N*log(N)가 소요된다.
log2(400만) ≈ 22 * 400만 = 대략 8,400만 정도이다.
여기에 이분탐색 + 경로 압축까지하면
시간복잡도는 꽤 높다.
하지만 통과하긴한다.
N까지 배열을 만든 후 존재 마킹 기반 정렬시 O(N)에 처리할 수 있다.
이는 Arrays.sort() 보다 2배 빠른 속도를 보여준다.
🔅 문제 풀이
import java.io.*;
import java.util.*;
import java.util.stream.IntStream;
public class Main {
static int[] parent;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringBuilder sb = new StringBuilder();
StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
int m = Integer.parseInt(st.nextToken());
int k = Integer.parseInt(st.nextToken());
int[] cards = new int[m];
boolean[] arr = new boolean[n + 1];
parent = IntStream.range(0, n + 1).toArray();
st = new StringTokenizer(br.readLine());
for (int i = 0; i < m; i++) {
int num = Integer.parseInt(st.nextToken());
arr[num] = true;
}
int idx = 0;
for (int i = 1; i <= n; i++) {
if (idx == m) break;
if (arr[i]) cards[idx++] = i;
}
st = new StringTokenizer(br.readLine());
for (int i = 0; i < k; i++) {
int target = Integer.parseInt(st.nextToken());
// 이분 탐색
int left = 0, right= m - 1;
while (left < right) {
int mid = (left + right) >>> 1;
if (cards[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
// 같은 값이면 안되고 더 큰 값중 가장 작은 값이여야하므로 바로 다음 칸의 값을 가리킴
if (cards[left] == target) {
left++;
}
int current = find(left); // 사용하지 않은 카드 찾기
sb.append(cards[current]).append("\n"); // 카드 사용
if (current + 1 < m) {
union(current, current+1); // 현재 카드는 사용하였으므로 다음 카드의 집합으로 추가한다.
}
}
System.out.print(sb.toString());
}
// a 는 현재 선택된 카드, b는 그 다음 카드
private static void union(int a, int b) {
a = find(a);
b = find(b);
parent[a] = b;
}
// 경로 압축
private static int find(int x) {
if (x == parent[x]) return x;
return parent[x] = find(parent[x]);
}
}