Bitmask
Escrito por Lawrence Melo
Bitmask (Máscara de bits) é uma técnica de força bruta utilizada para testar, de forma mais eficiente, todas as possibilidades de uma determinada situação. A bitmask se baseia na representação binária de um número, por exemplo, , onde os bits são indexados do e da direita pra esquerda.
Já que em cada bit do número só é possível encontrar os valores ou , podemos usar esse fato para representar situações. Por exemplo, suponha que queremos representar quais cidades já foram visitadas em uma viajem à 6 cidades distintas. Se já visitamos as cidades e , representamos a bitmask como . Se não visitamos nenhuma cidade . Da mesma forma, caso todas as cidades tenham sido visitadas .
Manipulação de bits
Para avançarmos em bitmask é necessário usarmos algumas operações binárias que são essenciais.
- Checar se o bit de um número está ligado:
Para checar se o bit está ligado, basta olhar se & é maior que , veja and binário.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
bool ligado(int n, int j) | |
{ | |
if(n & (1 << j)) return true; | |
else return false; | |
} |
- Ligar o bit de um número :
Para ligar o bit , basta fazer | , veja or binário.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void ligar(int &n, int j) | |
{ | |
n = n | (1 << j); | |
} |
Exemplos de uso de Bitmask
Exemplo 1
Você tem questões. Você estimou a dificuldade da i-ésima questão como . Agora você tem que preparar uma prova com essas questões tal que a dificuldade total das questões seja pelo menos e no máximo e que a diferença entre a questão mais difícil e a mais fácil seja pelo menos . Calcule a quantidade de maneiras de preparar a prova.
Restrições:
Solução:
Podemos armazenar numa bitmask que guarda as questões que estamos usando, ou seja, se o bit está ligando então estamos usando a i-ésima questão na prova. Então vamos percorrer todas as bitmasks possíveis e checar se ela satisfaz os requisitos para a prova. Segue a implementação:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <bits/stdc++.h> | |
using namespace std; | |
const int inf = 1e9 + 10; | |
int n, l , r, x; | |
int v[20]; | |
long long resp; | |
int main() | |
{ | |
ios::sync_with_stdio(false), cin.tie(); | |
cin >> n >> l >> r >> x; | |
for(int i = 0; i < n; i++) | |
cin >> v[i]; | |
for(int mask = 0; mask <= (1 << n) - 1; mask++)// percorro todas as bitmasks | |
{ | |
long long s = 0; | |
int maior = 0; | |
int menor = inf; | |
for(int i = 0; i < n; i++)// percorro todos os bits | |
{ | |
if(mask & (1 << i)) //checo se a questão está na prova que estou olhando | |
{ | |
s += v[i]; | |
maior = max(maior, v[i]); // calculo a maior dificuldade | |
menor = min(menor, v[i]); // calculado a menor dificuldade | |
} | |
} | |
if(s >= l and s <= r and (maior - menor) >= x) resp++; // se a prova é valida, adiciono um no contador | |
} | |
cout << resp << "\n"; | |
return 0; | |
} |
Complexidade: .
Exemplo 2
Temos cidades e estradas de mão dupla entre elas. Queremos saber o menor custo para visitar todas as cidades sem passar por uma cidade mais de uma vez, começando da cidade 0.
Restrições:
- .
- .
- A distância entre duas cidades é menor que .
- As cidades são enumeradas de à .
Solução:
Utilizaremos uma ideia de programação dinâmica com auxílio de bitmask, , onde representa o custo mínimo para visitar todas as cidades que estão marcadas na bitmask sendo que a última cidade visitada é a . Nesse caso, se a cidade está marcada, então o bit está ligado. Com isso, a recursão da programação dinâmica é:
=
Código para melhor entendimento:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <bits/stdc++.h> | |
using namespace std; | |
typedef long long ll; | |
const int maxn = 16, maxm = (1 << 15) + 10; | |
int n, m; | |
int mat[maxn][maxn], dp[maxm][maxn]; | |
int solve(int mask, int i) | |
{ | |
if(mask == (1 << n) - 1) return dp[mask][i] = 0; // caso todas as cidades já tenham sido visitadas | |
if(dp[mask][i] != -1) return dp[mask][i]; // se a DP ja foi calculada | |
int ans = 1e9; | |
for(int v = 0; v < n; v++) | |
{ | |
if(mask & (1 << v) or !mat[i][v]) continue; // se a cidade atual já foi visitada ou não há rota de i à v | |
ans = min(ans, solve((mask | (1 << v)), v) + mat[i][v]); //pego o minimo entre ir para cada vizinho | |
} | |
return dp[mask][i] = ans; | |
} | |
int main() | |
{ | |
ios::sync_with_stdio(false), cin.tie(nullptr); | |
cin >> n >> m; | |
while(m--) | |
{ | |
int a, b, w; | |
cin >> a >> b >> w; | |
mat[a][b] = mat[b][a] = w; | |
} | |
memset(dp, -1, sizeof(dp)); | |
cout << solve(1, 0) << "\n"; // começando da cidade 0 | |
} |
Complexidade: .
Exercícios para praticar:
Combate à Dengue | URI |
Rota do Taxista | URI |
Penalização | URI |
Artskjid | DMOJ |
Paths | KATTIS |
Kronican | COCI 2016 |
Preparing Olympiad | Codeforces |
Vitamins | Codeforces |
Matching | DMOJ |