이번 ν¬μŠ€νŠΈλŠ” PS ν…Œν¬λ‹‰ 쀑 ν•˜λ‚˜μΈ λΉ„νŠΈλ§ˆμŠ€ν¬(Bitmask) 기법을 λ‹€λ£Ήλ‹ˆλ‹€. Bitmask 기법은 μ΄μ§„μˆ˜(bit)와 μ΄μ§„μˆ˜ μ—°μ‚°(bitwise operation)을 ν™œμš©ν•΄ μ—°μ‚°μ—μ„œ 큰 이득을 μ œκ³΅ν•˜κΈ° λ•Œλ¬Έμ— κ³ κΈ‰ PSλ₯Ό μœ„ν•΄μ„œλŠ” κΌ­ μ΅ν˜€μ•Ό ν•˜λŠ” κΈ°λ²•μž…λ‹ˆλ‹€.

8 minute read

이번 ν¬μŠ€νŠΈλŠ” PS ν…Œν¬λ‹‰ 쀑 ν•˜λ‚˜μΈ λΉ„νŠΈλ§ˆμŠ€ν¬(Bitmask) 기법을 λ‹€λ£Ήλ‹ˆλ‹€. Bitmask 기법은 μ΄μ§„μˆ˜(bit)와 μ΄μ§„μˆ˜ μ—°μ‚°(bitwise operation)을 ν™œμš©ν•΄ μ—°μ‚°μ—μ„œ 큰 이득을 μ œκ³΅ν•˜κΈ° λ•Œλ¬Έμ— κ³ κΈ‰ PSλ₯Ό μœ„ν•΄μ„œλŠ” κΌ­ μ΅ν˜€μ•Ό ν•˜λŠ” κΈ°λ²•μž…λ‹ˆλ‹€.


μ΄μ§„μˆ˜μ™€ μ΄μ§„μˆ˜ μ—°μ‚°, 그리고 집합 ν‘œν˜„

컴퓨터곡학을 κ³΅λΆ€ν–ˆλ‹€λ©΄, μ΄μ§„μˆ˜μ™€ μ‹­μ§„μˆ˜ λ³€ν™˜μ— λŒ€ν•΄ 이미 λΉ μ‚­ν•˜κ²Œ μ•Œκ³  μžˆμ„ 것이닀. 또, &(AND), |(OR), ~(NOT)κ³Ό 같은 Bitwise μ΄μ§„μˆ˜ 연산에 λŒ€ν•΄μ„œλ„ 이미 μ΅μˆ™ν•  것이닀. BitmaskλŠ” μ΄μ§„μˆ˜ ν‘œκΈ°λ²•κ³Ό μ΄μ§„μˆ˜ 연산듀을 톡해 β€˜μ§‘ν•©β€˜μ„ ν‘œν˜„ν•˜λŠ” 기법이닀.

μ§‘ν•©μ˜ ν‘œν˜„μ€ Boolean Array둜 κ°€λŠ₯ν•˜λ‹€. bool mySet[100]κ³Ό 같은 μ½”λ“œμ—μ„œ λͺ‡λ²ˆμ§Έ λΉ„νŠΈκ°€ μΌœμ‘ŒλŠ”μ§€, κΊΌμ‘ŒλŠ”μ§€λ‘œ 집합을 ν‘œν˜„ν•  수 μžˆλ‹€. λ§Œμ•½ 2, 3, 5번째 λΉ„νŠΈκ°€ μΌœμ Έμžˆλ‹€λ©΄, 2, 3, 5번째 μ›μ†Œκ°€ μ‘΄μž¬ν•˜λŠ” 집합을 λ§ν•œλ‹€.

(Boolean Array)
| 0 | 1 | 1 | 0 | 1 | ...

κ·ΈλŸ¬λ‚˜ 이것을 Boolean Array λŒ€μ‹  Integer ν•˜λ‚˜λ‘œλ„ ν‘œν˜„μ΄ ν•  수 μžˆλ‹€.

(Integer)
01101...

Cμ–Έμ—μ„œ intν˜• λ³€μˆ˜μ˜ 4 λ°”μ΄νŠΈ, 즉 32 bit이기 λ•Œλ¬Έμ— 32개 μ›μ†Œλ₯Ό κ°–λŠ” 뢀뢄집합을 Integer λ³€μˆ˜ ν•˜λ‚˜λ‘œ ν‘œν˜„ν•  수 μžˆλ‹€. 더 큰 집합을 ν‘œν˜„ν•΄μ•Ό ν•œλ‹€λ©΄, int λŒ€μ‹  long long을 μ“Έ 수 μžˆλ‹€. 그러면 8 λ°”μ΄νŠΈ, 즉 64개 μ›μ†Œμ˜ 뢀뢄집합을 ν‘œν˜„ν•  수 μžˆλ‹€.

Bitmask μ§‘ν•©μ˜ μ—°μ‚°

Bitmask κΈ°λ²•μ˜ κ°€μž₯ 큰 μž₯점은 Bitwise 연산을 톡해 집합 연산을 λΉ λ₯΄κ²Œ μˆ˜ν–‰ν•  수 μžˆλ‹€λŠ” 점에 μžˆλ‹€.

1. 곡집합

int A = 0


2. 꽉 μ°¬ 집합

int A = (1 << 10) - 1

int N = (size of your set)
int A = (1 << N) - 1


3. μ›μ†Œ μΆ”κ°€

AλΌλŠ” Bitmask 집합에 k 번째 μ›μ†Œλ₯Ό μΆ”κ°€

0101 | 0010 -> 0111
A | (1 << k)


4. μ›μ†Œ μ‚­μ œ

AλΌλŠ” Bitmask 집합에 k 번째 μ›μ†Œλ₯Ό μ‚­μ œ. μ›μ†Œ μ‚½μž… λ•Œμ˜ μ—°μ‚°μ˜ λ°˜λŒ€κ²ƒμ„ μˆ˜ν–‰ν•˜λ©΄ λœλ‹€.

0101 & ~(0100) -> 0101 & (1011) -> 0001
A & ~(1 << k)


5. μ›μ†Œ μΆ”μΆœ

AλΌλŠ” Bitmask 집합에 k 번째 μ›μ†Œμ˜ μ‘΄μž¬μ—¬λΆ€λ₯Ό μΆ”μΆœ

if(0101 & 0100) -> if(0100) -> True
if(0101 & 0010) -> if(0000) -> False
A ^ (1 << k)


6. μ›μ†Œ ν† κΈ€

AλΌλŠ” Bitmask 집합에 k 번째 μ›μ†Œλ₯Ό ν† κΈ€

0101 ^ 0100 -> 0001
A ^ (1 << k)

μ§€κΈˆκΉŒμ˜ Bitmask 집합 μ—°μ‚° 쀑에 λ°˜λ³΅ν•΄μ„œ λ³΄μ΄λŠ” νŒ¨ν„΄μ€ (1 << k) μ˜€λ‹€. νŠΉμ • μ›μ†Œλ₯Ό 닀루기 μœ„ν•΄μ„  (1 << k)λ₯Ό μ“΄λ‹€λŠ” 점, κΈ°μ–΅ν•΄λ‘μž.


7. 집합 사이 μ—°μ‚°

  • A | B: A, B의 합집합. $A \cup B$
  • A & B: A, B의 ꡐ집합. $A \cap B$
  • A & (~B): A, B의 차집합. $A - B$
  • A ^ B: A와 B, λ‘˜ 쀑 ν•˜λ‚˜μ—λ§Œ ν¬ν•¨λœ μ›μ†Œλ“€μ˜ 집합.

λ§Œμ•½ 집합을 Boolean Array둜 ν‘œν˜„ν–ˆλ‹€λ©΄, μœ„μ˜ 집합 연산듀을 μˆ˜ν–‰ν•˜λŠ”λ° $O(n)$의 μ‹œκ°„μ΄ κ±Έλ¦°λ‹€, κ·ΈλŸ¬λ‚˜ Bitmask 집합 기법을 μ“΄λ‹€λ©΄, $O(1)$의 λΉ„μš©λ§Œ λ“€ 뿐이닀!

예제 및 ν™œμš©

μ§€κΈˆκΉŒμ§€ 기본적인 Bitmask 집합 연산듀을 μ‚΄νŽ΄λ³΄μ•˜λ‹€. μ΄λ²ˆμ—λŠ” Bitmaskλ₯Ό μ“°λŠ” λŒ€ν‘œμ μΈ 문제인 [μ™ΈνŒμ› 문제]와 Bitmask μ—°μ‚°μ˜ ν™œμš©λ“€μ„ μ‚΄νŽ΄λ³΄μž.

μ™ΈνŒμ› 문제

μ™ΈνŒμ› λ¬Έμ œμ—μ„œλŠ” λ°©λ¬Έν•œ λ„μ‹œλ₯Ό Bitmask μ§‘ν•©μœΌλ‘œ ν‘œν˜„ν•œλ‹€. 예λ₯Ό λ“€μ–΄ 001010이라면 2번째, 4번째 λ„μ‹œλ₯Ό λ°©λ¬Έν•œ 것이 λœλ‹€. λ°œμƒ μžμ²΄λŠ” μ™ΈνŒμ› 문제 ν¬μŠ€νŠΈμ—μ„œ ν™•μΈν•˜κ³  DPμ—μ„œ μ“°λŠ” μ ν™”μ‹λ§Œ λ”°λ‘œ 써보면 μ•„λž˜μ™€ κ°™λ‹€.

\[C(j, S) = \min_{i \in S, \; i \ne j} \left\{ C(i, S - \{j\}) + d_{ij}\right\}\]

λ§Œμ•½ visitedλ₯Ό Bitmask 집합이 μ•„λ‹ˆλΌ Boolean Array둜 ν‘œν˜„ν–ˆλ‹€λ©΄, μœ„μ™€ 같은 뢀뢄집합을 기반으둜 ν•˜λŠ” 점화식을 κ΅¬ν˜„ν•˜λŠ”κ²Œ μ•„μ£Ό κΉŒλ‹€λ‘œμ› μ„ 것이닀. Bitmask 집합을 μ‚¬μš©ν•˜λ©΄, 집합 $S$에 λŒ€ν•œ 뢀뢄을 μ •μˆ˜ λ³€μˆ˜λ‘œ μ‰½κ²Œ ν‘œν˜„ν•  수 μžˆλ‹€.

dp[curr][visited] = min(dp[prev, visited - prev] + w[prev][curr])

단, μ΄λ²ˆμ— μ‚΄νŽ΄λ³Έ DP+Bitmask의 μ™ΈνŒμ› λ¬Έμ œλŠ” λ°©λ¬Έν•˜λŠ” λ„μ‹œμ˜ μˆ˜κ°€ μ΅œλŒ€ 16개둜 μƒλ‹Ήνžˆ 적닀. λ§Œμ•½ λ°©λ¬Έν•΄μ•Ό ν•˜λŠ” λ„μ‹œ μˆ˜κ°€ Integer λ³€μˆ˜μ˜ ν‘œν˜„ λ²”μœ„λ₯Ό λ²—μ–΄λ‚œλ‹€λ©΄, Bitmask 집합 기법을 μ“Έ 수 μ—†λ‹€λŠ” 점에 μœ μ˜ν•˜μž.

ν™œμš©

Bitmask 집합 κΈ°λ²•μ˜ 좔가적인 ν™œμš©λ²•μ— λŒ€ν•΄ μ‚΄νŽ΄λ³΄μž.

8. μ΅œμ†Œ μ›μ†Œ μΆ”μΆœ

μ΄μ§„μˆ˜ A에 음수 -AλŠ” 2의 보수 ν‘œν˜„λ²•μ— μ˜ν•΄ ~A + 1둜 ν‘œν˜„λœλ‹€. μ΄μ§„μˆ˜ A의 κ°€μž₯ 였λ₯Έμͺ½ λΉ„νŠΈ k에 λŒ€ν•΄ 였λ₯Έμͺ½μ€ λͺ¨λ‘ 0으둜 ν‘œν˜„λ˜λŠ”λ°, ~A 연산은 kλ₯Ό 0으둜 그리고 κ·Έ 였λ₯Έμͺ½μ„ λͺ¨λ‘ 1둜 λ°”κΎΌλ‹€. μ—¬κΈ°μ„œ 1을 λ”ν•΄μ£Όκ²Œ 되면, k보닀 였λ₯Έμͺ½μ— μžˆλŠ” bitλŠ” λͺ¨λ‘ 0이 되고, k번째 λΉ„νŠΈλŠ” λ‹€μ‹œ 1이 λ˜μ§€λ§Œ, k의 μ™Όμͺ½μ— 있던 λΉ„νŠΈλŠ” ~A에 μ˜ν•΄ ν† κΈ€λœ μ±„λ‘œ κ·ΈλŒ€λ‘œ μžˆλ‹€.

 A = 011011000
-A = -(011011000) -> ~(011011000) + 1 (2의 보수) -> (100100111) + 1 -> 100101000
A & (-A) = 011011000 & 100101000 -> 000001000
int first = A & (-A)


9. μ΅œμ†Œ μ›μ†Œ μ‚­μ œ

011011000 & (011011000 - 1) -> 011011000 & (011010111) -> 011010000
A &= (A - 1)

A-1을 ν•˜κ²Œ 되면, κ°€μž₯ 였λ₯Έμͺ½μ— 있던 bitλŠ” 0이 되고, 그보닀 였λ₯Έμͺ½μ— μžˆλŠ” bitλŠ” λͺ¨λ‘ 1이 λœλ‹€. 이λ₯Ό ν™œμš©ν•΄ μ΅œμ†Œ μ›μ†Œλ₯Ό μ‚­μ œν•œλ‹€.


10. λͺ¨λ“  뢀뢄집합 순회

for (int subset = A; subset; subset = (subset - 1) & A) { ... }
A = 1011

[subset]
                                     1011 (μ΄ˆκΈ°κ°’)
(1011 - 1) & 1011  -> 1010 & 1011 -> 1010
(1010 - 1) & 1011  -> 1001 & 1011 -> 1001
(1001 - 1) & 1011  -> 1000 & 1011 -> 1000
(1000 - 1) & 1011  -> 0111 & 1011 -> 0011
(0011 - 1) & 1011  -> 0110 & 1011 -> 0010

AλΌλŠ” μ§‘ν•©μ˜ 뢀뢄집합을 μˆœνšŒν•˜λŠ” μ½”λ“œλ‹€. 본인은 μΌμ’…μ˜ ν…Œν¬λ‹‰μœΌλ‘œ 받아듀이고 μ“°κ³  μžˆλ‹€. μœ„μ˜ μ½”λ“œλŠ” AλΌλŠ” μ§‘ν•©μ˜ 뢀뢄집합을 μˆœνšŒν•˜λŠ” μ½”λ“œμΈλ°, μ•½κ°„ λ³€ν˜•ν•΄ 전체 집합을 μˆœνšŒν•˜λŠ” μ½”λ“œλ„ λ§Œλ“€ 수 μžˆλ‹€.

for (int subset = 0, subset < (1 << N); subset++) { ... }

References