diff --git "a/Algorithm/\353\271\204\355\212\270\353\247\210\354\212\244\355\201\254(BitMask).md" "b/Algorithm/\353\271\204\355\212\270\353\247\210\354\212\244\355\201\254(BitMask).md" new file mode 100644 index 00000000..ff007704 --- /dev/null +++ "b/Algorithm/\353\271\204\355\212\270\353\247\210\354\212\244\355\201\254(BitMask).md" @@ -0,0 +1,195 @@ +## 비트마스크(BitMask) + +> 집합의 요소들의 구성 여부를 표현할 때 유용한 테크닉 + +
+ +##### *왜 비트마스크를 사용하는가?* + +- DP나 순열 등, 배열 활용만으로 해결할 수 없는 문제 +- 작은 메모리와 빠른 수행시간으로 해결이 가능 (But, 원소의 수가 많지 않아야 함) +- 집합을 배열의 인덱스로 표현할 수 있음 + +- 코드가 간결해짐 + +
+ +##### *비트(Bit)란?* + +> 컴퓨터에서 사용되는 데이터의 최소 단위 (0과 1) +> +> 2진법을 생각하면 편하다. + +
+ +우리가 흔히 사용하는 10진수를 2진수로 바꾸면? + +`9(10진수) → 1001(2진수)` + +
+ +#### 비트마스킹 활용해보기 + +> 0과 1로, flag 활용하기 + +[1, 2, 3, 4 ,5] 라는 집합이 있다고 가정해보자. + +여기서 임의로 몇 개를 골라 뽑아서 확인을 해야하는 상황이 주어졌다. (즉, 부분집합을 의미) + +``` +{1}, {2} , ... , {1,2} , ... , {1,2,5} , ... , {1,2,3,4,5} +``` + +물론, 간단히 for문 돌려가며 배열에 저장하며 경우의 수를 구할 순 있다. + +하지만 비트마스킹을 하면, 각 요소를 인덱스처럼 표현하여 효율적인 접근이 가능하다. + +``` +[1,2,3,4,5] → 11111 +[2,3,4,5] → 11110 +[1,2,5] → 10011 +[2] → 00010 +``` + +집합의 i번째 요소가 존재하면 `1`, 그렇지 않으면 `0`을 의미하는 것이다. + +이러한 2진수는 다시 10진수로 변환도 가능하다. + +`11111`은 10진수로 31이므로, 부분집합을 **정수를 통해 나타내는 것**이 가능하다는 것을 알 수 있다. + +> 31은 [1,2,3,4,5] 전체에 해당하는 부분집합에 해당한다는 의미! + +이로써, 해당 부분집합에 i를 추가하고 싶을때 i번째 비트를 1로만 바꿔주면 표현이 가능해졌다. + +이런 행위는 **비트 연산**을 통해 제어가 가능하다. + +
+ +#### 비트 연산 + +> AND, OR, XOR, NOT, SHIFT + +- AND(&) : 대응하는 두 비트가 모두 1일 때, 1 반환 + +- OR(|) : 대응하는 두 비트 중 모두 1이거나 하나라도 1일때, 1 반환 + +- XOR(^) : 대응하는 두 비트가 서로 다를 때, 1 반환 + +- NOT(~) : 비트 값 반전하여 반환 + +- SHIFT(>>, <<) : 왼쪽 혹은 오른쪽으로 비트 옮겨 반환 + + - 왼쪽 시프트 : `A * 2^B` + - 오른쪽 시프트 : `A / 2^B` + + ``` + [왼 쪽] 0001 → 0010 → 0100 → 1000 : 1 → 2 → 4 → 8 + [오른쪽] 1000 → 0100 → 0010 → 0001 : 8 → 4 → 2 → 1 + ``` + +
+ +비트연산 연습문제 : [백준 12813](https://www.acmicpc.net/problem/12813) + +##### 구현 코드(C) + +```C +#include + +int main(void) { + unsigned char A[100001] = { 0, }; + unsigned char B[100001] = { 0, }; + unsigned char ret[100001] = { 0, }; + int i; + + scanf("%s %s", &A, &B); + + // AND + for (i = 0; i < 100000; i++) + ret[i] = A[i] & B[i]; + puts(ret); + + // OR + for (i = 0; i < 100000; i++) + ret[i] = A[i] | B[i]; + puts(ret); + + // XOR + for (i = 0; i < 100000; i++) + ret[i] = A[i] != B[i] ? '1' : '0'; + puts(ret); + + // ~A + for (i = 0; i < 100000; i++) + ret[i] = A[i] == '1' ? '0' : '1'; + puts(ret); + + // ~B + for (i = 0; i < 100000; i++) + ret[i] = B[i] == '1' ? '0' : '1'; + puts(ret); + + return 0; +} +``` + +
+ +연습이 되었다면, 다시 비트마스크로 돌아와 비트연산을 활용해보자 + +크게 삽입, 삭제, 조회로 나누어 진다. + +
+ +#### 1.삽입 + +현재 이진수로 `10101`로 표현되고 있을 때, i번째 비트 값을 1로 변경하려고 한다. + +i = 3일 때 변경 후에는 `11101`이 나와야 한다. 이때는 **OR연산을 활용**한다. + +``` +10101 | 1 << 3 +``` + +`1 << 3`은 `1000`이므로 `10101 | 01000`이 되어 `11101`을 만들 수 있다. + +
+ +#### 2.삭제 + +반대로 0으로 변경하려면, **AND연산과 NOT 연산을 활용**한다. + +``` +11101 & ~1 << 3 +``` + +`~1 << 3`은 `10111`이므로, `11101 & 10111`이 되어 `10101`을 만들 수 있다. + +
+ +#### 3.조회 + +i번째 비트가 무슨 값인지 알려면, **AND연산을 활용**한다. + +``` +10101 & 1 << i + +3번째 비트 값 : 10101 & (1 << 3) = 10101 & 01000 → 0 +4번째 비트 값 : 10101 & (1 << 4) = 10101 & 10000 → 10000 +``` + +이처럼 결과값이 0이 나왔을 때는 i번째 비트 값이 0인 것을 파악할 수 있다. (반대로 0이 아니면 무조건 1인 것) + +이러한 방법을 활용하여 문제를 해결하는 것이 비트마스크다. + +
+ +비트마스크 연습문제 : [백준 2098](https://www.acmicpc.net/problem/2098) + +
+ +해당 문제는 모든 도시를 한 번만 방문하면서 다시 시작점으로 돌아오는 최소 거리 비용을 구해야한다. + +완전탐색으로 답을 구할 수는 있지만, N이 최대 16이기 때문에 16!으로 시간초과에 빠지게 된다. + +따라서 DP를 활용해야 하며, 방문 여부를 배열로 관리하기 힘드므로 비트마스크를 활용하면 좋은 문제다. \ No newline at end of file