Algorithm

Bitmasking(비트마스킹) 알고리즘

JWonK 2023. 7. 7. 03:50
728x90
반응형

비트연산자 활용법

 

아래에 기록하는 비트연산자 활용법은 모두 암기해야 한다.

비트 연산 코드
idx번째 비트 끄기 S &= -(1 << idx)
idx번째 비트 XOR 연산 S ^= (1 << idx)
최하위 켜져있는 비트 찾기 idx = (S & -S)
크기가 n인 집합의 모든 비트를 켜기 (1 << n) - 1
idx번째 비트 켜기 S |= (1 << idx)
idx번째 비트가 켜져 있는지 확인하기 if(S & (1 << idx))

 

 

 

#1. idx번째 비트 끄기


idx번째 비트끄기 S &= -(1 << idx)

 

예를 들어, S = 101010(18)에서 10010 빨간 부분의 1을 0으로 변경하고 싶으면?

그럼 1번째 비트이다.

 

먼저 000010을 만든다. (← [1 << 1]을 하면 만들 수 있다.)

그 다음 비트를 뒤집어준다.

→ 111101로 만들어진다.

 

여기서 S = 101010과 AND(&) 연산을 수행한다.

결과는 101000으로 만들어진다.

 

#include <bits/stdc++.h>

using namespace std;

int main(){
    int S = 18;
    int idx = 1;
    
    S &= -(1 << idx);
    
    cout << S <<'\n'; // 16
    
    return 0;
}

 

 

 

 

 

 

#2. idx번째 비트 XOR 연산(0은 1로, 1은 0으로)


idx번째 비트 XOR 연산 S ^= (1 << idx)

 

이건 컴퓨터 구성에서 배웠던 내용으로 간단한 내용이다.

XOR 연산은 두 수가 모두 0 또는 1일 때 0을 반환한다.

즉, 두 수가 다를 경우에는 1을 반환한다.

 

따라서 우리가 바꾸고자 하는 자리에 1과 XOR연산을 취하게 되면 0은 1과 다르기 때문에 1이 되는 것이고, 1은 1과 같기 때문에 0으로 변환하는 것이다.

 

#include <bits/stdc++.h>

using namespace std;

int main(){
    int S = 18;
    int idx = 1;
    
    S ^= (1 << idx);
    
    cout << S <<'\n'; // 16
    
    return 0;
}

 

 

 

 

 

 

#3. 최하위 켜져있는 비트 찾기


최하위 켜져있는 비트 찾기 idx.= (S & -S)

 

2의 보수 개념이 등장하기 때문에 알아두어야 한다.

 

S = 10010이라고 가정한다.

-S = -(S + 1)이기 때문에

01101 + 1 = -S이다.

-S = 01110이며

 

10010

    &

01110

 

= 00010이 반환된다.

 

 

#include <bits/stdc++.h>

using namespace std;

int main(){
    int S = 18;
    int idx = (S & -S);
    
    cout << idx <<'\n';
    
    return 0;
}

 

 

 

 

 

 

#4. 크기가 n인 집합의 모든 비트를 켜기


크기가 n인 집합의 모든 비트를 켜기 (1 << n) - 1

 

이진수 / 비트연산자 / 비트마스킹을 배우는 이유는 이러한 비트들을 하나의 배열처럼 쓰기 위함이다.

 

예를 들어,

1111은

"0번째, 1번째, 2번째, 3번째 요소가 "포함"되어 있다"라고 표현할 수 있다.

 

배열 크기가 4인 boolean배열로 나타내는 것이 아닌 15라는 정수 하나로 나타낼 수 있기 때문에 메모리상 이득을 취할 수 있다.

 

만약, 위 [1, 1, 1, 1]에서 1번째는 포함하지 않는다면 [1, 1, 0, 1]로 나타낼 수 있다.

 

이를 비트연산자를 활용해서 표현해보자면

크기가 4라면 

16 (1 << 4)로 표현할 수 있고

(1 << 4) - 1을 하게 되면 15가 나온다.

 

15는 앞서 설명한 [1, 1, 1, 1]과 동일하다. 

이렇게 크기가 4인 집합을 하나의 숫자로 나타낸다면 (1 << n) -1로 나타내면 된다.

 

#include <bits/stdc++.h>

using namespace std;

int main(){
    int n = 4;
    cout << (1 << n) - 1 << '\n'; // 15
    
    return 0;
}

 

 

 

 

 

 

 

#5. idx번째 비트를 켜기


idx번째 비트를 켜기 S |= (1 << idx)

 

10010(18)에서 0번째 비트를 켜면? 10011(19)가 된다.

 

이를 S |= (1 << idx)로 나타낼 수 있다.

 

|는 or을 나타낸다. 둘 중 하나라도 1을 나타내면 1로 변환할 수 있다.

따라서 바꾸고자 하는 S에 비트 켜기를 위한 자리수 (1 << idx)의 값과 or연산을 취하면 된다.

 

#include <bits/stdc++.h>

using namespace std;

int main(){
	int S = 18;
    int idx = 0;
    
    S |= (1 << idx);
    cout << S << '\n'; // 19
    
    return 0;
}

 

 

 

 

 

#6. idx번째 비트가 켜져있는지 확인하기


idx번째 비트가 켜져있는지 확인하기 if(S & (1 << idx))

 

이것은 너무나 쉽다. 비트가 켜져있어야 하는 것은 1을 의미하며 1을 얻기 위해서 AND연산을 취해 1일 때만 1이 출력되도록 구현하면 된다.

 

#include <bits/stdc++.h>

using namespace std;

int main(){
	int S = 18;
    int idx = 0;
    
    if(S & (1 << idx)){
        cout << "해당 idx : " << idx << "가 켜져있습니다.\n";
    } else{
        cout << "해당 idx : " << idx << "가 꺼져있습니다.\n";
    }
    
    return 0;
}

 

 

 

 

 

# 비트마스킹을 이용한 경우의 수


비트마스킹은 경우의 수를 표현하는데도 잘 쓰인다.

예를 들어, {사과, 딸기, 포도,, 배}의 모든 경우의 수는 어떻게 될까

 

{사과}

{딸기, 배}

{사과, 딸기, 포도}

...

 

이렇게 총 16가지가 나올 수 있다.

사과를 포함하거나 포함하지 않거나 해서 각각의 요소는 2개의 상태값을 가지기 때문에 2^4 = 16이 된다.

 

#include <bits/stdc++.h>

using namespace std;

const int n = 4;

int main(){
    string a[n] = {"사과", "딸기", "포도", "배"};
    for(int i = 0; i < (1 << n); i++){
        string ret = "";
        for(int j = 0; j < n; j++){
            if(i & (1 << j)){
                ret += (a[j] + " ");
            }
        }
        cout << ret << '\n';
    }
    
    return 0;
}

 

이렇게 모든 집합을 표현할 수 있다.

i는 0000, 0001, 0010을 상징ㅎ한다.

j는 0, 1, 2, 3을 기반으로 (1 << 0), (1 << 1) 등으로 해당 번째의 비트가 켜져있냐 꺼져있냐를 통해 집합을 확인한다.

 

즉, 이러한 것을 통해 4C1, 4C2, ... , 4C4 의 조합들의 모든 경우의 수를 한 번의 for문 만으로 표현이 가능한 것이다.

 

 

 

 

 

# 비트마스킹을 이용한 매개변수 전달


사과라는 매개변수가 포함이 되어있고 이어서 사과 + 포도, 사과 + 배 이런 식의 매개변수를 더하는 것을 구현하고 싶다면 이렇게 하면 된다

 

#include <bits/stdc++.h>

using namespace std;

const int n = 4;
string a[n] = {"사과", "딸기", "포도", "배"};

void go(int num){
    string ret = "";
    for(int i = 0; i < 4; i++){
        if(num & (1 << i)) ret += a[i] + " ";
    }
    cout << ret << '\n';
    return;
}

int main(){
    for(int i=1;i<n;i++){
        go(1 | (1 << i));
    }
    
    return 0;
}
728x90
반응형

'Algorithm' 카테고리의 다른 글

누적합(prefix sum) 알고리즘  (3) 2023.06.18
Trie - ex) BOJ - 14725번 : 개미굴  (0) 2023.02.28
[다익스트라 알고리즘] (C/C++) 백준 - 5972번  (0) 2023.01.17
Union-Find 알고리즘  (0) 2021.08.21