https://www.acmicpc.net/problem/11723
11723번: 집합
첫째 줄에 수행해야 하는 연산의 수 M (1 ≤ M ≤ 3,000,000)이 주어진다. 둘째 줄부터 M개의 줄에 수행해야 하는 연산이 한 줄에 하나씩 주어진다.
www.acmicpc.net
접근 방법
문제를 보자마자 떠오르는 방법은 HashSet 자료 구조를 이용하는 것이었다. 그래서 먼저 HashSet으로 문제를 해결 한 후, 비트마스킹을 학습하고자 리팩토링 해보았다.
HashSet
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
public class Main{
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int M = Integer.parseInt(br.readLine());
StringBuffer sb = new StringBuffer();
Set<Integer> set = new HashSet<>();
for (int i = 0; i < M; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
String command = st.nextToken();
int input = -1;
switch(command) {
case "add":
set.add(Integer.parseInt(st.nextToken()));
break;
case "remove":
set.remove(Integer.parseInt(st.nextToken()));
break;
case "check":
input = Integer.parseInt(st.nextToken());
if (set.contains(input)) {
sb.append(1);
} else {
sb.append(0);
}
sb.append("\n");
break;
case "toggle":
input = Integer.parseInt(st.nextToken());
if (set.contains(input)) {
set.remove(input);
} else {
set.add(input);
}
break;
case "all":
set = new HashSet<>();
for (int j = 1; j <= 20; j++) {
set.add(j);
}
break;
case "empty":
set.clear();
break;
}
}
System.out.print(sb.toString());
}
}
해당 코드에서 HashSet 자료 구조를 이용하는 부분만 비트마스킹으로 변경하였다.
비트마스킹
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main{
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int M = Integer.parseInt(br.readLine());
StringBuffer sb = new StringBuffer();
int set = 0;
for (int i = 0; i < M; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
String command = st.nextToken();
int input = -1;
switch(command) {
case "add":
input = Integer.parseInt(st.nextToken());
set = set | (1 << input);
break;
case "remove":
input = Integer.parseInt(st.nextToken());
set = set & ~(1 << input);
break;
case "check":
input = Integer.parseInt(st.nextToken());
sb.append(((set & (1 << input)) > 0) ? "1\n" : "0\n");
break;
case "toggle":
input = Integer.parseInt(st.nextToken());
set = set ^ (1 << input);
break;
case "all":
set = (1 << 21) - 1;
break;
case "empty":
set = 0;
break;
}
}
System.out.print(sb.toString());
}
}
input된 숫자를 그대로 사용하기 위해 최하위 비트인 LSB는 더미 데이터로 사용하였다.
1. add
설명을 위해 set을 4비트의 1000(2)로 가정해보겠다.
input이 2라고 들어왔을 때
1은 0001(2)이므로, 1 << input 은 0100(2)이 된다.
원소 Idx | 3 | 2 | 1 | 0 |
set | 1 | 0 | 0 | 0 |
1 << Input | 0 | 1 | 0 | 0 |
set | (1 << Input) | 1 | 1 | 0 | 0 |
이 상태에서 1000(2) | 0100(2)의 비트연산을 하면, idx가 2인 원소를 제외하고는 원래 값이 유지가 되면서 idx2는 반드시 1이 되게 된다.
2. remove
1 << input(=2)을 비트 부정 연산자를 이용해 1의 보수를 구하면 1011(2)가 된다.
원소 Idx | 3 | 2 | 1 | 0 |
set | 1 | 1 | 0 | 0 |
~ (1 << Input) | 1 | 0 | 1 | 1 |
set & ~(1 << Input) | 1 | 0 | 0 | 0 |
원래의 set과 &연산을 시키면 idx2는 무조건 0이 되므로, 해당 비트에 해당하는 원소가 remove 되었다고 볼 수 있다.
3. check
원소 Idx | 3 | 2 | 1 | 0 |
set | 1 | 0 | 0 | 0 |
1 << Input | 0 | 1 | 0 | 0 |
set & (1 << Input) | 0 | 0 | 0 | 0 |
체크할 idx가 2일 경우, 1 << 2와 &연산을 시키면 set에 따라 0100 혹은 0000이 나오게 된다.
즉 idx n이 set에 포함되어 있을 경우 &연산의 결과가 항상 0보다 크므로 이를 기준으로 삼는다.
4. toggle
원소 Idx | 3 | 2 | 1 | 0 |
set | 1 | 1 | 0 | 0 |
1 << Input | 0 | 1 | 0 | 0 |
set ^ (1 << input) | 1 | 0 | 0 | 0 |
toggle의 경우, set의 idx2가 1일 경우 0이 되어야하고 0일 경우 1이 되어야한다.
즉 비트의 관점에서 봤을 때 1과 xor 연산이 필요하다.
5. all
set의 모든 비트를 1로 만들어줘야하므로 set이 n비트일 경우, (1 << n+1) - 1을 해준다.
1 << 5는 10000(2)인데, 여기서 1을 빼면 1111(2)이 되기 때문이다. int타입일 경우 정확히는 0000_0000_0000_0000_ 0000_0000_0000_1111(2)이겠지만 우리는 4비트까지만 신경쓸 것이므로 상관하지 않아도 된다.
6. empty
모든 비트를 0으로 초기화 해주어야 하므로 set = 0이다.
'Algorithm' 카테고리의 다른 글
[Java] 백준 23309번 - 철도 공사 (3) | 2025.07.22 |
---|---|
[Java] 백준 13172번 - Σ(시그마) (2) | 2024.05.02 |
[Java] 백준 1149번 - RGB거리 (1) | 2024.02.10 |
[Java] 백준 1244번 - 스위치 켜고 끄기 (2) | 2024.01.17 |
[Java] SWEA - 두 개의 숫자열 (2) | 2024.01.11 |