본문으로 건너뛰기
김신건의 로그

DP on Bitmask: 비트마스크 DP

· 수정 · 📖 약 2분 · 866자/단어 #algorithm #dp #bitmask #subset
DP on Bitmask, Bitfield DP, 비트마스크 DP, bitmask dp, 집합 DP

정의

부분집합 상태를 비트마스크로 인코딩하여 DP를 수행하는 기법. 정수 maski번째 비트가 1이면 “원소 i 선택”, 0이면 “미선택”을 의미한다. 원소 수 N ≤ 20 정도의 집합 문제에서 유효하다.

비트 연산 기초: DP Bitfield 참조.

문제 상황

“어떤 원소를 선택했는가” 자체가 상태인 최적화 문제:

  • TSP (Traveling Salesman Problem): 모든 도시를 한 번씩 방문하는 최소 비용 순환 경로
  • Assignment Problem: 사람과 태스크를 1:1로 매칭하여 비용 최소화
  • Hamiltonian Path: 모든 정점을 방문하는 경로 존재 여부
  • Set Cover: 최소 개수의 집합으로 전체 원소를 커버

N ≤ 20이면 2^20 = 1,048,576으로 메모리와 시간 모두 허용 범위.

시각화

N=3 TSP 상태 전이 (도시 0 고정 출발):

flowchart LR
    S["출발: 도시 0만 방문"]
    A["도시 0, 1 방문, 현재 i=1"]
    B["도시 0, 2 방문, 현재 i=2"]
    C["도시 0, 1, 2 방문, 현재 i=2"]
    D["도시 0, 1, 2 방문, 현재 i=1"]
    E["종료 후보: i=2에서 복귀"]
    F["종료 후보: i=1에서 복귀"]

    S -->|"도시 1 선택"| A
    S -->|"도시 2 선택"| B
    A -->|"도시 2 선택"| C
    B -->|"도시 1 선택"| D
    C --> E
    D --> F

N=3 비트마스크 표현:

mask (이진)십진선택된 도시
0000공집합
00110
01021
0113{0, 1}
10042
1015{0, 2}
1106{1, 2}
1117{0, 1, 2}

핵심 아이디어

상태를 (mask, i) 쌍으로 표현:

  • mask: 현재까지 방문/선택한 원소들의 비트마스크
  • i: 현재 위치 (마지막으로 선택한 원소)

전이: 미방문 원소 j를 다음에 선택 → (mask | (1<<j), j)

핵심 비트 연산:

  • i번째 포함 여부: mask & (1 << i) 가 0이 아니면 포함
  • i번째 원소 추가: mask | (1 << i) 로 새 mask 생성
  • i번째 원소 제거: mask & ~(1 << i)
  • 전체 집합: (1 << N) - 1 (모든 비트 1)
  • 부분집합 순회: for (int s = mask; s > 0; s = (s-1) & mask)

TSP 점화식

초기: dp[1][0] = 0 (도시 0만 방문, 도시 0에 위치), 나머지 INF. : min(dp[(1<<N)-1][i] + cost[i][0]) for i in 1..N-1.

Assignment Problem 변형

dp[i][mask] = i번째 사람까지 처리, 사용된 태스크 집합 = mask.

TSP와 달리 외부 루프가 사람 인덱스 i, 내부 루프가 mask. popcount(mask) == i 조건으로 유효 상태만 처리.

부분집합 열거 패턴

모든 부분집합을 열거하는 표준 패턴:

// mask의 모든 부분집합 열거 (공집합 포함)
for (int s = mask; ; s = (s - 1) & mask) {
    process(s);
    if (s == 0) break;
}

모든 mask에 대해 실행하면 O(3^N) (각 원소가 mask 포함/부분집합 포함/부분집합 미포함 3가지).

알고리즘

# TSP 비트마스크 DP
dp[1][0] = 0          # 도시 0 방문, 도시 0 위치
for mask = 1 to (1<<N)-1:
    for i = 0 to N-1:
        if dp[mask][i] == INF: continue
        if not (mask & (1<<i)): continue  # i가 mask에 없으면 무효 상태
        for j = 0 to N-1:
            if mask & (1<<j): continue    # 이미 방문한 도시
            nmask = mask | (1 << j)
            dp[nmask][j] = min(dp[nmask][j], dp[mask][i] + cost[i][j])

full = (1 << N) - 1
answer = min(dp[full][i] + cost[i][0] for i in 1..N-1)  # 원점 복귀

구현

#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int n;
  cin >> n;
  vector<vector<int>> cost(n, vector<int>(n));
  for (int i = 0; i < n; i++)
      for (int j = 0; j < n; j++)
          cin >> cost[i][j];

  // dp[mask][i]: mask 집합 방문 후 i에 있을 때 최소 비용
  vector<vector<int>> dp(1 << n, vector<int>(n, INF));
  dp[1][0] = 0;

  for (int mask = 1; mask < (1 << n); mask++) {
      for (int i = 0; i < n; i++) {
          if (dp[mask][i] == INF) continue;
          if (!(mask & (1 << i))) continue;
          for (int j = 0; j < n; j++) {
              if (mask & (1 << j)) continue;
              int nmask = mask | (1 << j);
              dp[nmask][j] = min(dp[nmask][j], dp[mask][i] + cost[i][j]);
          }
      }
  }

  int full = (1 << n) - 1;
  int ans = INF;
  for (int i = 1; i < n; i++)
      ans = min(ans, dp[full][i] + cost[i][0]);

  cout << ans << "\n";
  return 0;
}
stdin
4
0 10 15 20
10 0 35 25
15 35 0 30
20 25 30 0
결과
80

복잡도

항목
시간O(2^N · N²)
공간O(2^N · N)
N=152^15 · 225 ≈ 7.4×10^6 (여유)
N=202^20 · 400 ≈ 4.2×10^8 (타이트)
실용 한계N ≤ 20 (int 기준 메모리 약 80MB)

WARNING

N=25 이상은 2^25 ≈ 33M 상태로 메모리와 시간 모두 한계 초과. N 제약 반드시 확인.

함정

1. 배열 크기: 스택 오버플로

// 지역 변수로 선언하면 스택 오버플로
int dp[1 << 20][20];  // 전역 선언 필수 (약 80MB)

2. 출발 도시 고정 누락

TSP는 순환 경로이므로 도시 0 고정 출발이 최적. 모든 도시에서 시작하면 정답이 N배 중복으로 잘못 계산될 수 있다.

3. mask에 i 포함 여부 체크 누락

if (!(mask & (1 << i))) continue;
// 없으면 i가 mask에 없는 잘못된 상태에서 전이 발생

4. 원점 복귀 비용 누락

dp[(1<<N)-1][i]는 i에 있는 비용. 최종 답은 dp[full][i] + cost[i][0]로 원점 복귀 포함.

5. Assignment Problem과 TSP 혼동

Assignment Problem에서는 dp[i][mask] = i번째 사람까지 처리, mask = 사용된 태스크. 전이 방향이 TSP와 다르므로 혼동 주의.

BOJ 연습 문제

번호제목유형
BOJ 2098외판원 순회TSP (핵심 문제)
BOJ 1194달이 차오른다, 가자.BFS + bitmask
BOJ 17471게리맨더링지역 분할
BOJ 1562계단 수Digit DP + bitmask
BOJ 2234성곽지역 합병 bitmask

관련 위키

이 글의 용어 (5개)
비트마스크 DP (Bitmask DP)algorithm
정의 비트마스크 DP (Bitmask DP) 는 상태 공간이 부분집합 으로 표현될 때, 각 부분집합을 정수의 비트로 인코딩해 DP 상태로 삼는 기법. N ≤ 20 범위에서 O(2…
DP Optimization: CHT, D&C, Knuth, SMAWKalgorithm
정의 일반 DP 를 O(N²) 또는 O(N³) 에서 O(N log N) 또는 O(N) 으로 낮추는 여러 최적화 기법의 총칭입니다. 모두 특정 조건 (monotone, concav…
Hamiltonian Path: 모든 정점 한 번씩algorithm
정의 Hamiltonian Path 는 그래프의 모든 정점을 정확히 한 번씩 방문하는 경로. 시작 = 끝이면 Hamiltonian Cycle. 일반 그래프에서 NP-complet…
SOS DP (Sum Over Subsets)algorithm
정의 SOS DP (Sum Over Subsets) 는 길이 2^N 의 배열 a 에 대해, 모든 mask (0..2^N-1) 마다 mask 의 부분 집합 (submask) 에 대…
TSP (Traveling Salesman Problem): 외판원 순회algorithm
정의 N 개 도시를 정확히 한 번씩 방문 후 시작점으로 돌아오는 최소 비용 경로. NP-hard. 공식 표현: N 개 정점의 완전 그래프에서 Hamiltonian Cycle 중 …

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기