비트마스킹 (Bitmask)
정의
비트마스킹 (Bitmask) 은 집합 / 상태 를 정수의 비트 패턴 으로 인코딩해 집합 연산을 비트 연산으로 처리하는 기법.
{0, 2, 3} 같은 집합을 0b1101 = 13 으로 표현하면, 멤버 확인 / 추가 / 제거 / 합집합 / 교집합 모두 &, |, ^, ~ 한 줄.
크기 N ≤ 20 (또는 25) 인 작은 집합의 모든 부분집합 순회 + DP 의 정형. Bitfield DP, SOS DP 의 기반.
문제 상황과 동기
“N 명을 선택하는 방법은 2^N 개. N=20 이면 약 10^6. 모든 부분집합을 보고 점수 최대 / 최소를 구하라”
- naive set / vector: 한 부분집합당 메모리 / 시간 O(N). 총 O(N · 2^N) = 2·10^7. 빡빡.
- bitmask: 한 부분집합을 int 하나 로. O(1) operations. 총 O(2^N) ~ O(N · 2^N).
핵심 통찰: 원소 N 이 32 이하면, 한 부분집합 = 한 정수 = 한 캐시 라인 안에서 모든 연산.
시각화
핵심 아이디어
각 원소를 비트 위치 에 대응. 집합 S 의 비트 표현:
S = {0, 2, 3, 5} → mask = (1<<0) | (1<<2) | (1<<3) | (1<<5) = 0b101101 = 45
핵심 연산
| 의미 | 연산 |
|---|---|
원소 i 가 S 에 있나? | (mask >> i) & 1 |
원소 i 추가 | mask | (1 << i) |
원소 i 제거 | mask & ~(1 << i) |
원소 i 토글 | mask ^ (1 << i) |
|S| (popcount) | __builtin_popcount(mask) |
| 합집합 | mask1 | mask2 |
| 교집합 | mask1 & mask2 |
| 차집합 | mask1 & ~mask2 |
| 대칭차 | mask1 ^ mask2 |
| 부분집합? | (mask1 & mask2) == mask1 |
모든 부분집합 순회
for mask = 0..(1<<N)-1:
// mask = 한 부분집합
부분집합의 부분집합 순회 (SOS 의 기반)
sub = mask
while sub > 0:
// sub = mask 의 부분집합 중 하나
sub = (sub - 1) & mask
// sub = 0 한번 더 처리
총 시간 O(3^N) (각 원소가 in/out/없음 3 상태).
알고리즘
// 모든 부분집합에서 어떤 값을 최대화
best = 0
for mask = 0..(1<<N)-1:
score = 0
for i = 0..N-1:
if (mask >> i) & 1:
score += value[i]
best = max(best, score)
구현
// N 개 아이템 중 무게합 ≤ W 인 부분집합의 최대 가치합 (N ≤ 20)
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, W; cin >> n >> W;
vector<int> w(n), v(n);
for (auto& x : w) cin >> x;
for (auto& x : v) cin >> x;
int best = 0;
for (int mask = 0; mask < (1 << n); mask++) {
int sw = 0, sv = 0;
for (int i = 0; i < n; i++) if ((mask >> i) & 1) {
sw += w[i]; sv += v[i];
}
if (sw <= W) best = max(best, sv);
}
cout << best << "\n";
}4 10
2 3 4 5
3 4 5 613(N=4 아이템, 무게합 ≤ 10. 최적: {0,1,2,3} 다 못 담으니 {0,2,3} 무게 11 안 됨, {0,1,3} 무게 10, 가치 13)
복잡도
| 작업 | 비용 |
|---|---|
| 모든 부분집합 순회 | O(2^N) |
| 각 부분집합의 부분집합 | O(3^N) (모든 mask 누적) |
| 단일 비트 연산 | O(1) |
| popcount | O(1) (__builtin_popcountll) |
| N 의 한계 | 20~25 정도까지 실용. 그 이상은 Subset Sum / DP 필요 |
변형 / 활용
| 패턴 | 예시 |
|---|---|
| **[[DP Bitfield | 비트필드 DP]]** |
| **[[SOS DP | Sum over Subsets DP]]** |
| FWHT | 비트 연산 컨볼루션 |
| State compression | 작은 그리드 / 자릿수 상태 |
| Bitset Optimization | 큰 비트 배열 (N > 64) |
함정
1. 32-bit overflow
N = 32 이상이면 1 << N 이 int overflow. 1LL << N 사용 (long long).
2. 음수 shift
1 << 31 의 결과는 signed int 의 부호 비트, undefined-ish 행동. unsigned 또는 long long 사용.
3. popcount 가 느린 환경
Java < 5 또는 일부 임베디드 환경에서는 Integer.bitCount 가 SW 구현. PS 에서는 보통 hardware popcnt.
4. 부분집합 순회의 중첩
for mask in 0..(1<<N)-1 안에서 또 for i in 0..N-1 로 비트 검사하면 O(N · 2^N). 이걸 막으려면 popcount + table 또는 SOS 변환.
표준 라이브러리
| 언어 | 비트 카운트 | 비트 마스크 |
|---|---|---|
| C++ | __builtin_popcountll, __builtin_ctzll, std::popcount (C++20) | std::bitset<N> (큰 N) |
| Python | bin(x).count('1'), x.bit_count() (3.10+) | 내장 int 자체 |
| Java | Integer.bitCount, Long.bitCount | BitSet |
| Rust | u32::count_ones | bit-vec crate |
BOJ 연습 문제
| 번호 | 제목 | 정답률 | 링크 |
|---|---|---|---|
| BOJ 11723 | 집합 | - | kokoa-lab |
| BOJ 1182 | 부분수열의 합 | - | kokoa-lab |
| BOJ 2098 | 외판원 순회 | - | kokoa-lab |
| BOJ 1052 | 물병 | - | kokoa-lab |
참고
이 글의 용어 (6개)
- 비트마스크 DP (Bitmask DP)algorithm
- 정의 비트마스크 DP (Bitmask DP) 는 상태 공간이 부분집합 으로 표현될 때, 각 부분집합을 정수의 비트로 인코딩해 DP 상태로 삼는 기법. N ≤ 20 범위에서 O(2…
- Bitset Optimizationalgorithm
- 정의 Bitset Optimization 은 불리언 / 비트 단위 정보 를 또는 배열에 패킹해, 한 명령어로 64 비트씩 병렬 연산 함으로써 시간 복잡도를 /64 (또는 , w …
- DP on Bitmask: 비트마스크 DPalgorithm
- 정의 부분집합 상태를 비트마스크로 인코딩 하여 DP 를 수행. 원소 수 N ≤ 20 정도에서 유효. 자세한 내용은 DP Bitfield 참조. 대표 예 TSP = 방문한 도시 m…
- FWHT (Fast Walsh-Hadamard Transform)algorithm
- 정의 FWHT (Fast Walsh-Hadamard Transform) 은 비트 연산 (XOR, AND, OR) 에 대한 컨볼루션 을 O(N log N) 에 계산하는 변환. FF…
- SOS DP (Sum Over Subsets)algorithm
- 정의 SOS DP (Sum Over Subsets) 는 길이 2^N 의 배열 a 에 대해, 모든 mask (0..2^N-1) 마다 mask 의 부분 집합 (submask) 에 대…
- Subset Sum: 부분집합 합algorithm
- 정의 집합 {a1, ..., an} 에서 부분집합의 원소 합이 특정 값 T 가 되는 것이 존재하는가. NP-complete in general, but pseudo-polynom…
💬 댓글