Skip to Content

Enumeración de submáscaras

Enumerar todas las submáscaras de una máscara dada

Dada una máscara de bits mm, se quiere iterar de forma eficiente por todas sus submáscaras, es decir, máscaras ss en las que solo están activados bits que ya estaban incluidos en la máscara mm.

Consideremos la implementación de este algoritmo, basada en trucos con operaciones de bits:

int s = m; while (s > 0) { ... se puede usar s ... s = (s-1) & m; }

o, usando una sentencia for más compacta:

for (int s=m; s; s=(s-1)&m) ... se puede usar s ...

En ambas variantes del código, la submáscara igual a cero no se procesará. Podemos procesarla fuera del bucle, o usar un diseño menos elegante, por ejemplo:

for (int s=m; ; s=(s-1)&m) { ... se puede usar s ... if (s==0) break; }

Examinemos por qué el código anterior visita todas las submáscaras de mm, sin repetición, y en orden descendente.

Supongamos que tenemos una máscara de bits actual ss, y queremos pasar a la siguiente máscara de bits. Al restarle una unidad a la máscara ss, eliminamos el bit activado más a la derecha y todos los bits a su derecha se vuelven 1. Luego quitamos todos los bits uno “de más” que no están incluidos en la máscara mm y por lo tanto no pueden formar parte de una submáscara. Hacemos esa eliminación usando la operación bit a bit (s-1) & m. Como resultado, “recortamos” la máscara s1s-1 para determinar el valor más alto que puede tomar, es decir, la siguiente submáscara después de ss en orden descendente.

Así, este algoritmo genera todas las submáscaras de esta máscara en orden descendente, realizando solo dos operaciones por iteración.

Un caso especial es cuando s=0s = 0. Después de ejecutar s1s-1 obtenemos una máscara con todos los bits activados (representación de bits de -1), y después de (s-1) & m tendremos que ss será igual a mm. Por lo tanto, hay que tener cuidado con la máscara s=0s = 0: si el bucle no termina en cero, el algoritmo puede entrar en un bucle infinito.

Iterar por todas las máscaras con sus submáscaras. Complejidad O(3n)O(3^n)

En muchos problemas, especialmente los que usan programación dinámica con máscaras de bits, se quiere iterar por todas las máscaras de bits y, para cada máscara, iterar por todas sus submáscaras:

for (int m=0; m<(1<<n); ++m) for (int s=m; s; s=(s-1)&m) ... s y m ...

Demostremos que el bucle interno ejecutará un total de O(3n)O(3^n) iteraciones.

Primera demostración: Consideremos el ii-ésimo bit. Hay exactamente tres opciones para él:

  1. no está incluido en la máscara mm (y por lo tanto no está incluido en la submáscara ss),
  2. está incluido en mm, pero no está incluido en ss, o
  3. está incluido tanto en mm como en ss.

Como hay un total de nn bits, habrá 3n3^n combinaciones distintas.

Segunda demostración: Nótese que si la máscara mm tiene kk bits activados, entonces tendrá 2k2^k submáscaras. Como hay un total de (nk)\binom{n}{k} máscaras con kk bits activados (véase coeficientes binomiales), entonces el número total de combinaciones para todas las máscaras será:

k=0n(nk)2k\sum_{k=0}^n \binom{n}{k} \cdot 2^k

Para calcular este número, nótese que la suma anterior es igual a la expansión de (1+2)n(1+2)^n usando el teorema del binomio. Por lo tanto, hay 3n3^n combinaciones, como queríamos demostrar.

Problemas de práctica