[코딩 테스트 준비 - 기초] 7. 비트마스크(Bitmask)
1. 비트 연산
NOT, AND, OR, XOR
A | B | ~A | A & B | A | B | A ^ B |
0 | 0 | 1 | 0 | 0 | 0 |
0 | 1 | 1 | 0 | 1 | 1 |
1 | 0 | 0 | 0 | 1 | 1 |
1 | 1 | 0 | 1 | 1 | 0 |
not 연산의 경우 자료형에 따라 결과가 달라지는 것을 주의해야 한다.
특히, unsigned와 signed에 따라서 정수로 보여지는 값이 달라진다. (2의 보수 때문)
Shift Left, Shift Right
A << B
- A를 B비트만큼 왼쪽으로 밀기
A x (2^B)
- ex.
3 << 4
= 3 x 2^4 = 3 x 16 = 48
A >> B
- A를 B비트만큼 오른쪽으로 밀기
A / (2^B)
- ex.
2 >> 4
= 2 / 2^4 = 2 / 16 = 1 / 8
2. 비트마스크
개요
비트마스크는 비트 연산을 이용해 부분집합을 표현하는 것이다.
- 비트마스크를 통해 집합을 정수로 나타낼 수 있다.
- 단, 이때 집합에 저장할 수 있는 수의 범위가 정해져 있어야 한다.
- 따라서 보통 0부터 N-1까지 N개의 정수로 이루어진 집합을 나태낼 때 사용한다.
ex) N = 10일때, s = {1, 3, 4, 5, 9}라는 집합에서 각 수의 존재여부를 나타내면 다음과 같다.
9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
1 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 0 |
이를 정수로 나타내면, 1000111010(2) = 570와 같다.
비트마스크의 장점
1. 공간을 적게 사용할 수 있다.
- 위 예시에서 집합 s를 표현하는 데에는 1, 3, 4, 5, 9라는 5개의 정수가 필요하지만,
- 비트마스크를 통해 표현하면 570 단 하나의 정수만 사용한다.
2. 연산의 시간복잡도가 O(1)
로 빠르다. 아래서 살펴보자.
cf) 비트마스크가 정수라는 것
이는 장점이 될 수도, 단점이 될 수도 있다.
장: 리스트나 배열로 표현할 때 정수는 간편하다.
단: 정수는 32bits 혹은 64bits로 범위에 제한이 있어, 수의 범위가 너무 크면 사용할 수 없다.
-> C++의 경우 이러한 단점을 보완하기 위해 bitset이라는 것을 제공한다. (but, 사용할 일 별로 없다고 함)
비트마스크의 연산
집합 S에대해 수 X를 검사, 추가, 삭제, 토글하는 방법
cf) 연산을 수행할 때 연산자 우선 순위 때문에 걱정되면 괄호를 사용하자!
1. S에 X가 있는지 검사
= S의 X번째 비트가 1인지 0인지 검사
= X번째 비트만 1로 두고 AND 연산 수행
= S & (1 << X)
2. S에 X를 추가
= S의 X번째 비트를 1로 변경
= X번째 비트만 1로 두고 OR 연산 수행
= S | (1 << X)
3. S에서 X를 삭제
= S의 X번째 비트를 0으로 변경
= X번째 비트만 0으로 두고 AND 연산 수행
= S & ~(1 << X)
4. S에서 X를 토글
= S의 X번째 비트가 0이면 1로, 1이면 0으로 변경
= X번째 비트만 1로 두고 XOR 연산 수행
= S ^ (1 << X)
결론
- 그냥 배열을 사용하는 것이 편리하지만, 비트마스크를 사용하는 이유는 집합을 배열의 인덱스로 표현할 수 있기 때문이다.
- 상태 다이나믹을 할 때 자주 사용하게 된다.
- 적은 갯수를 저장하는 집합에서 사용하는 것이 적합하다.
3. 문제를 통해 알아보기
[백준 11723 집합]
- 비트마스크로 풀기 위해서는 0부터 19까지로 생각해야 한다. (즉, x를 -1해서 계산해야 함)
- all과 empty는 x를 입력받지 않는다!! (당연한건데 이것 때문에 1시간 날림..)
- c++로 구현할 경우 입력이 너무 많아서 빠른 입출력을 해줘야 한다.
#include <iostream>
using namespace std;
int n = 20;
int main(){
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int m;
cin>>m;
int s = 0; //공집합
string cmd;
while(m--){
cin>>cmd;
int x;
if(cmd == "add"){
cin>>x;
x--;
s = (s | (1 << x));
} else if(cmd == "remove"){
cin>>x;
x--;
s = (s & ~(1 << x));
} else if(cmd == "check"){
cin>>x;
x--;
int check = (s & (1 << x));
cout<<(check ? 1 : 0)<<'\n';
} else if(cmd == "toggle"){
cin>>x;
x--;
s = (s ^ (1 << x));
} else if(cmd == "all"){
s = (1 << n) - 1;
} else{ //empty
s = 0;
}
}
return 0;
}
[백준 1182 부분수열의 합]
- 가능한 모든 부분수열을 만들어 확인한다.
- 수열의 위치(인덱스)를 기준으로, 특정 위치의 값이 부분수열에 포함되는지 아닌지 확인하면 된다
- 즉, 특정 값의 부분수열 포함 여부를 판단하는 것은 0과 1로 충분하므로 비트마스크를 이용할 수 있다.
- 0부터 n-1까지의 수를 이진수로 표현하면, 배열의 각 위치에 0 또는 1이 들어가게 된다.
- ex) n = 5일때, 0 = 00000, 1 = 00001, 10 = 01010, 31 = 111111
- 즉, 공집합을 제외하고 1부터 전체집합까지 비트마스크 i로 표현하고
- i에 j가 들어있는지 검사(i를 이진수로 봤을 때 j번째가 0인지 1인지)한다.
#include <iostream>
using namespace std;
int main(){
int n, s;
cin>>n>>s;
int arr[n];
for(int i=0; i<n; i++) cin>>arr[i];
int bs = (1<<n) - 1;
int ans = 0;
for(int i=1; i<=bs; i++){
int sum = 0;
for(int j=0; j<n; j++){
//비트마스크 i에 j가 들어있는지 check
int check = (i&(1<<j));
if(check) sum += arr[j];
}
if(sum == s) ans++;
}
cout<<ans<<'\n';
return 0;
}
'Algorithm > 코딩 테스트 준비 - 기초' 카테고리의 다른 글
[코딩 테스트 준비 - 기초] 9. 그래프 - DFS & BFS (0) | 2022.02.01 |
---|---|
[코딩 테스트 준비 - 기초] 8. 다이나믹 프로그래밍(DP) (0) | 2022.01.24 |
[코딩 테스트 준비 - 기초] 6. 브루트 포스 - 순열 (0) | 2022.01.10 |
[코딩 테스트 준비 - 기초] 5. 브루트 포스 - 재귀 & 백트래킹 (0) | 2022.01.05 |
[코딩 테스트 준비 - 기초] 4. 브루트 포스 - N과 M & NM과 K (0) | 2021.12.31 |