0→1→3→2→0: 10+25+30+15 = 80. TSP는 n이 20 이하일 때 비트마스크 DP가 적합하다. n이 크면 2^n이 너무 커진다.
3. 시간 복잡도
유형
시간 복잡도
공간 복잡도
비고
LCS
O(m × n)
O(m × n)
m, n: 두 문자열 길이
편집 거리
O(m × n)
O(m × n)
1차원 최적화 가능
0/1 배낭
O(n × W)
O(n × W) → O(W)
1차원 배열로 최적화
팰린드롬 DP
O(n²)
O(n²)
행렬 연쇄 곱셈
O(n³)
O(n²)
TSP (비트마스크)
O(2^n × n²)
O(2^n × n)
n ≤ 20 실용적
4. 유형별 접근 전략
2차원 DP 중 어떤 방식을 써야 하는지 판별하는 기준이다.
두 개의 입력 시퀀스가 동시에 관계를 맺는 경우
두 문자열, 두 배열이 주어지고 공통 요소를 찾거나 변환 비용을 계산해야 한다. LCS, 편집 거리가 해당된다.
하나의 입력에 두 가지 제약이 동시에 있는 경우
물건 개수와 무게 용량처럼 두 개의 변수가 독립적으로 상태를 구성한다. 배낭 문제가 해당된다.
구간 [i, j]에 대한 최적해를 구해야 하는 경우
배열의 부분 구간을 분할하거나 구성하는 방식을 선택해야 한다. 행렬 연쇄 곱셈, 팰린드롬 분할이 해당된다.
방문 상태를 집합으로 표현해야 하는 경우
노드를 모두 방문해야 하는 최적화 문제에서 상태 수가 2^n으로 제한될 때 비트마스크 DP를 쓴다. TSP가 대표적이다.
2차원 DP에서 가장 중요한 것은 여전히 상태 정의다. dp[i][j]가 무엇을 의미하는지 명확히 설정하지 않으면 점화식을 세울 수 없다. 상태 정의 후 점화식이 더 작은 인덱스만 참조하는지, 초기값이 명확한지, 계산 순서가 올바른지 점검해야 한다.
5. Python 자주 쓰는 메서드
구간 DP 순회 패턴
n = 5
dp = [[0] * n for _ inrange(n)]
for length inrange(2, n + 1):
for i inrange(n - length +1):
j = i + length -1print(f"구간 [{i}, {j}] (길이 {length})")
구간 [0, 1] (길이 2)
구간 [1, 2] (길이 2)
구간 [2, 3] (길이 2)
구간 [3, 4] (길이 2)
구간 [0, 2] (길이 3)
구간 [1, 3] (길이 3)
구간 [2, 4] (길이 3)
구간 [0, 3] (길이 4)
구간 [1, 4] (길이 4)
구간 [0, 4] (길이 5)
for length in range(2, n+1): 구간 길이 순서로 순회
for i in range(n - length + 1): 시작점 순회
j = i + length - 1: 끝점 계산
비트마스크 기본 연산
n = 4
mask = 0
mask = mask | (1<<1)
print(f"1번 비트 설정: {bin(mask)}")
mask = mask | (1<<3)
print(f"3번 비트 설정: {bin(mask)}")
print(f"1번 비트 확인: {bool(mask & (1<<1))}")
print(f"2번 비트 확인: {bool(mask & (1<<2))}")
mask = mask ^ (1<<1)
print(f"1번 비트 토글: {bin(mask)}")
full = (1<< n) -1print(f"전체 방문 상태: {bin(full)}")
1번 비트 설정: 0b10
3번 비트 설정: 0b1010
1번 비트 확인: True
2번 비트 확인: False
1번 비트 토글: 0b1000
전체 방문 상태: 0b1111
연산
코드
설명
i번째 비트 마스크 생성
1 << i
i번 비트만 1인 정수
i번째 비트 설정
mask | (1 << i)
i번 비트를 1로
i번째 비트 확인
mask & (1 << i)
결과가 0이면 미설정
i번째 비트 토글
mask ^ (1 << i)
0이면 1로, 1이면 0으로
전체 방문 상태
(1 << n) - 1
n개 비트 모두 1
bin() — 비트 상태 디버깅
states = [0, 1, 5, 10, 15]
for s in states:
print(f"{s:3d} → {bin(s):>8s} → {bin(s)[2:].zfill(4)}")