June

비트마스킹

비트마스킹

비트마스킹은 정수의 이진수 표현을 활용해 집합이나 상태를 효율적으로 표현하고 조작하는 기법이다.

핵심 아이디어

정수 하나의 각 비트를 켜짐/꺼짐 플래그로 사용하는 방법이다.

예를 들어 원소가 {0, 1, 2, 3} 인 집합에서 부분집합 {0, 2} 는 이진수 0101, 즉 정수 5로 표현할 수 있다.

기본 연산

1. 특정 비트 켜기 (원소 추가)mask |= (1 << i) — i번째 비트를 1로 설정

2. 특정 비트 끄기 (원소 제거)mask &= ~(1 << i) — i번째 비트를 0으로 설정

3. 특정 비트 확인 (원소 포함 여부)if (mask & (1 << i)) — i번째 비트가 1인지 검사

4. 특정 비트 토글mask ^= (1 << i) — i번째 비트를 반전

5. 집합 연산 합집합은 a | b, 교집합은 a & b, 차집합은 a & ~b로 계산

왜 쓰는가

배열이나 set으로 부분집합을 관리하면 메모리도 많이 쓰고 연산도 느린데, 비트마스크를 쓰면 정수 하나로 최대 64개 원소의 부분집합을 표현할 수 있고, 집합 연산이 O(1)에 끝난다. DP에서 상태를 표현할 때 특히 뛰어나다.

대표적인 활용 예시

비트마스크 DP (외판원 문제 등)

c++
// dp[mask][v] = mask에 포함된 도시들을 방문하고 현재 v에 있을 때의 최소 비용
// mask의 i번째 비트가 1이면 도시 i를 이미 방문했다는 뜻

for (int mask = 0; mask < (1 << n); mask++) {
    for (int v = 0; v < n; v++) {
        if (!(mask & (1 << v))) continue; // v가 mask에 없으면 skip
        for (int u = 0; u < n; u++) {
            if (mask & (1 << u)) continue; // u를 이미 방문했으면 skip
            int next = mask | (1 << u);
            dp[next][u] = min(dp[next][u], dp[mask][v] + dist[v][u]);
        }
    }
}

한계

시간복잡도 면에서 n개 원소의 모든 부분집합은 2^n개라서, 보통 n ≤ 20 정도일 때 비트마스크 DP를 적용할 수 있다.

#2098 외판원 순회

비트마스크 DP의 상태는 dp[현재 도시][방문한 도시들의 집합]으로 정의한다. 여기서 방문한 도시들의 집합을 정수 하나의 이진수 표현으로 나타낸다.

visited 변수는 각 비트가 해당 도시의 방문 여부를 나타낸다. 예를 들어 visited = 0101이면 도시 0과 2를 방문한 상태를 의미한다.

초기 호출은 tsp(n, 0, 1)로 시작한다. 1은 이진수 0001이므로 0번 도시만 방문한 상태에서 출발한다는 뜻이다.

종료 조건은 visited == (1 << n) - 1로 확인한다. 모든 비트가 1이면 모든 도시를 방문한 것이므로, 출발점으로 돌아가는 비용 w[start][0]을 리턴한다.

이미 방문한 도시인지는 visited & (1 << i)로 검사한다. i번째 비트가 켜져 있으면 이미 방문한 도시이므로 건너뛴다.

다음 상태로의 전이는 visited | (1 << i)로 수행한다. 기존 방문 집합에 도시 i를 추가한 새로운 집합을 만든다.

메모이제이션은 dp[start][visited] != -1로 확인한다. 이미 계산한 상태면 저장된 값을 바로 리턴하여 중복 계산을 방지한다.

시간복잡도는 O(n² × 2^n)이다. 상태 수가 n × 2^n개이고 각 상태에서 최대 n개의 도시를 탐색하기 때문이다.

c++
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

vector<vector<int>> w;
vector<vector<int>> dp;

int tsp(int n, int start, int visited) {
    if (visited == (1 << n) - 1) {
        if (w[start][0] == 0) return -1;
        return w[start][0];
    }

    if (dp[start][visited] != -1) {
        return dp[start][visited];
    }

    dp[start][visited] = 1e9;
    for(int i = 0 ; i < n ; i++) {
        if(w[start][i] == 0 || (visited & (1 << i))) continue;
        int next = tsp(n, i, visited | (1 << i));
        if(next != -1) {
            dp[start][visited] = min(dp[start][visited], next + w[start][i]);
        }
    }
    return dp[start][visited] == 1e9 ? -1 : dp[start][visited];
}

int main() {
    int n;
    cin >> n;
    w.resize(n, vector<int>(n));
    dp.resize(n, vector<int>(1 << n, -1));

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cin >> w[i][j];
        }
    }

    cout << tsp(n, 0, 1) << endl;
}

Seoul, South Korea

jwsong5160@gmail.com

© 2026 Junwoo Song