1. 문제 정보
- 문제 번호: 11723번
- 문제 이름: 집합
- 문제 요약:
- 비어있는 집합 $S$에 대해 add, remove, check, toggle, all, empty 연산을 수행하는 프로그램을 작성하라. 원소는 1부터 20까지의 정수이며, 수행해야 하는 연산의 수는 최대 3,000,000개이다.
2. 접근 방법 (알고리즘)
❌ 첫 번째 시도: std::set 사용 (시간 초과)
처음에는 원소를 관리하기 위해 std::set<int>을 사용했다.
- 원인: set의 각 연산은 O(log N)이지만, 300만 개의 연산을 처리하기에는 메모리 할당과 트리 균형 유지 등의 오버헤드가 너무 크다.
- 시간 복잡도: O(M log N), 여기서 M은 연산 횟수이다. 결과는 시간 초과였다.
✅ 최종 접근: 비트마스킹 (성공)
문제에서 주어지는 원소의 범위가 1~20으로 매우 작다는 점에 주목했다.
- 핵심 아이디어: 정수형 변수(int)는 32비트이므로, 각 비트를 하나의 원소로 취급할 수 있다.
- 예를 들어, 3번 비트가 1이면 집합에 숫자 3이 포함된 것으로, 0이면 포함되지 않은 것으로 간주한다.
- 시간 복잡도: 모든 연산이 비트 연산 한 번으로 끝나므로 O(1)이다. 300만 건의 연산도 순식간에 처리 가능하다.
3. 코드 구현 (정답)
#include <iostream>
#include <string>
using namespace std;
int main() {
// 입출력 최적화
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
int bit = 0; // 집합을 나타낼 비트마스크 변수
for (int i = 0; i < n; i++) {
string order;
cin >> order;
if (order == "add") {
int num;
cin >> num;
// num번째 비트를 1로 만듦 (OR 연산)
bit |= (1 << num);
}
else if (order == "remove") {
int num;
cin >> num;
// num번째 비트를 0으로 만듦 (NOT 후 AND 연산)
bit &= ~(1 << num);
}
else if (order == "check") {
int num;
cin >> num;
// num번째 비트가 서 있는지 확인
if (bit & (1 << num)) cout << 1 << "\n";
else cout << 0 << "\n";
}
else if (order == "toggle") {
int num;
cin >> num;
// num번째 비트를 반전시킴 (XOR 연산)
bit ^= (1 << num);
}
else if (order == "all") {
// 모든 비트를 1로 만듦 (1부터 20까지)
bit = (1 << 21) - 1;
}
else if (order == "empty") {
// 모든 비트를 0으로 만듦
bit = 0;
}
}
return 0;
}
4. 배운 점 & 회고 (TIL) ✍️
비트마스킹은 단순히 '기술'이 아니라, 데이터를 바라보는 '관점'의 전환임을 배웠다.
① 비트 연산의 마법
비트 연산자를 활용하면 집합 연산을 하드웨어 수준에서 처리할 수 있어 매우 빠르다.
- 1 << num: 숫자 1을 num만큼 왼쪽으로 밀어서 특정 위치만 1인 필터를 만든다.
- | (OR): 특정 원소 추가.
- & ~ (AND NOT): 특정 원소 삭제.
- ^ (XOR): 특정 원소가 있으면 삭제, 없으면 추가 (Toggle).
- & (AND): 특정 원소 존재 여부 확인.
② 정수형 변수의 효율성
int 타입은 보통 4바이트(32비트)이다. 문제에서 원소 범위가 1~20이었기 때문에 int 변수 하나만으로도 충분히 모든 상태를 표현할 수 있었다. 만약 범위가 64까지였다면 long long을 사용했을 것이다. 자료구조를 사용하는 것보다 메모리 사용량이 극적으로 줄어든다는 점이 놀랍다.
③ 'All' 연산의 최적화
all 연산 시 반복문을 돌며 insert 하는 대신, 비트 연산 한 번으로 모든 비트를 1로 채울 수 있다. (1 << 21) - 1은 0번부터 20번 비트까지 모두 1로 채워진 값을 의미한다. (실제 원소는 1~20이므로 0번 비트는 무시된다.)
④ 요약
데이터의 개수가 적고(보통 30개 이하), 존재 여부(True/False)만 관리하면 되는 상황이라면 복잡한 set이나 배열 대신 비트마스킹이 정답이다. O(1)의 속도와 극도의 메모리 효율성을 동시에 챙길 수 있는 아주 강력한 무기이다.
'알고리즘 문제풀이 > C++' 카테고리의 다른 글
| [백준/C++] 9095번: 1, 2, 3 더하기 - DP의 정석을 맛보다 (0) | 2026.03.02 |
|---|---|
| [백준/C++] 2839번: 설탕 배달 - 브루트 포스에서 DP로 나아가기 (0) | 2026.02.28 |
| [백준/C++] 1764번: 듣보잡 - set_intersection과 inserter 활용하기 (0) | 2026.02.22 |
| [백준/C++] 10816번: 숫자 카드 2 - unordered_map과 operator[]의 동작 원리 (1) | 2026.02.13 |
| [백준/C++] 2164번: 카드2 - Vector의 함정과 Queue의 효율성 (0) | 2026.02.12 |