DP con máscaras de bits
DP con máscaras de bits
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| CSES | Hamiltonian Flights | Fácil | Bitmasks | en el módulo |
Tutorial
| Fuente | Recurso | Notas |
|---|---|---|
| CPH | 10.5 - DP on Bits, 19.2 - Hamiltonian Paths | Elevator Rides, SOS, hamiltonianos |
| nwatx | A Primer on Bitmask DP | |
| PAPS | 9.4 - Subset DP | ejemplo - similar a hamiltoniano |
| CF | DP Over Subsets | recorridos hamiltonianos |
| DPCC | 6 - Bitmasking | Diagrama |
| HE | DP and Bit Masking |
Solución
Sea la cantidad de rutas que visitan todas las ciudades del subconjunto y terminan en la ciudad . Las transiciones serán entonces:
donde es el subconjunto sin la ciudad .
Complejidad temporal:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAX_N = 20;
const ll MOD = (ll)1e9 + 7;
ll dp[1 << MAX_N][MAX_N];
// come_from[i] contiene las ciudades que pueden volar a i
vector<int> come_from[MAX_N];
int main() {
int city_num;
int flight_num;
cin >> city_num >> flight_num;
for (int f = 0; f < flight_num; f++) {
int start, end;
cin >> start >> end;
come_from[--end].push_back(--start);
}
dp[1][0] = 1;
for (int s = 2; s < 1 << city_num; s++) {
// solo considerar subconjuntos que tienen la primera ciudad
if ((s & (1 << 0)) == 0) continue;
// además, solo considerar subconjuntos con la última ciudad si es el subconjunto completo
if ((s & (1 << (city_num - 1))) && s != ((1 << city_num) - 1)) continue;
for (int end = 0; end < city_num; end++) {
if ((s & (1 << end)) == 0) continue;
// el subconjunto que no incluye el end actual
int prev = s - (1 << end);
for (int j : come_from[end]) {
if ((s & (1 << j))) {
dp[s][end] += dp[prev][j];
dp[s][end] %= MOD;
}
}
}
}
cout << dp[(1 << city_num) - 1][city_num - 1] << '\n';
}import sys
MOD = 10**9 + 7
input = sys.stdin.readline
n, m = map(int, input().strip().split())
dp = [[0 for _ in range(n)] for _ in range(1 << n)]
dp[1][0] = 1
adj = [[] for _ in range(n)]
for i in range(m):
a, b = map(int, input().strip().split())
adj[b - 1].append(a - 1)
for cities in range(1, 1 << n):
# solo considerar ciudades que incluyen la primera ciudad
if not cities & 1:
continue
# solo considerar subconjuntos con la última ciudad si es el subconjunto completo
if cities & (1 << n - 1) and cities != (1 << n) - 1:
continue
# recorrer posibles ciudades de llegada
for end in range(1, n):
if not cities & (1 << end):
continue
prev_cities = cities ^ (1 << end)
for prev_end in adj[end]:
if not prev_cities & (1 << prev_end):
continue
dp[cities][end] += dp[prev_cities][prev_end]
dp[cities][end] %= MOD
print(dp[(1 << n) - 1][n - 1])Fusionar subconjuntos
En algunos problemas, para un conjunto , no alcanza con transicionar desde . En cambio, hace falta transicionar desde todos los subconjuntos estrictos de .
Aunque pueda parecer que hay que hacer transiciones, ¡en realidad solo hay transiciones!
Para ver por qué, contemos la cantidad de pares ordenados donde . En lugar de contar de forma directa, nótese que cada elemento está en una de estas situaciones:
- En y en
- En ninguno
- En pero no en . Si está en pero no en , no es un subconjunto válido.
Dado que cada elemento puede estar en tres estados posibles, la complejidad total es en realidad .
Para implementar esto, podemos usar algunos trucos bit a bit:
for (int mask = 0; mask < (1 << n); mask++) {
for (int submask = mask; submask != 0; submask = (submask - 1) & mask) {
int subset = mask ^ submask;
// hacer lo que se necesite aquí
}
}Cuando restamos de , el bit más a la derecha se invierte a y todos los bits a su derecha se vuelven . Aplicar el AND bit a bit con saca todos los bits extra que no están en . Con este proceso podemos obtener todos los subconjuntos estrictos en orden creciente calculando , que hace resta de conjuntos.
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| AC | Close Group | Fácil | Bitmasks | en el módulo |
Explicación
El objetivo de este problema es particionar los nodos en conjuntos tales que los nodos de cada conjunto formen un grafo completo. Sea la cantidad mínima de particiones tales que en cada partición el grafo formado es un grafo completo.
Primero podemos hallar qué conjuntos forman un grafo completo, fijando en e en caso contrario. Esto se puede hacer de forma naive en o representando la lista de adyacencia como una máscara de bits y usando manipulación de bits para ver si un conjunto de nodos es un grafo completo.
Luego podemos transicionar así:
Implementación
Complejidad temporal:
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<int> adj(n);
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
u--;
v--;
// lista de adyacencia representada como máscara de bits
adj[u] |= (1 << v);
adj[v] |= (1 << u);
}
vector<int> dp(1 << n, INT32_MAX);
for (int mask = 0; mask < (1 << n); mask++) {
bool connected = true;
for (int u = 0; u < n; u++) {
if (((mask >> u) & 1) != 0) {
// comprobar si u está conectado a todos los demás nodos de mask
if (((adj[u] | (1 << u)) & mask) != mask) {
connected = false;
break;
}
}
}
if (connected) { dp[mask] = 1; }
}
for (int mask = 0; mask < (1 << n); mask++) {
for (int submask = mask; submask; submask = (submask - 1) & mask) {
int subset = mask ^ submask;
// submask tiene todo lo de mask que no está en subset
if (dp[subset] != INT32_MAX && dp[submask] != INT32_MAX) {
dp[mask] = min(dp[mask], dp[subset] + dp[submask]);
}
}
}
cout << dp[(1 << n) - 1] << endl;
}Problemas
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| AC | Matching | Fácil | Bitmasks, DP | Solución | |
| AC | Grouping | Fácil | Bitmasks, DP | Solución | |
| CF | Team Building | Fácil | Bitmasks, MinCostFlow | Solución | |
| Old Gold | Guard Mark | Fácil | Bitmasks, DP | Solución | |
| Old Gold | Moovie Mooving | Fácil | Bitmasks, DP | Solución | |
| Gold | Uddered but not Herd | Normal | Bitmasks, DP | — | |
| CSES | Elevator Rides | Normal | Bitmasks, DP | Solución | |
| IZhO | 2014 - Bank | Normal | Bitmasks, DP | Solución | |
| Kattis | Cat & Mice | Normal | Bitmasks, DP, Geometry, Binary Search | Solución | |
| Gold | Friendship Editing | Difícil | Bitmasks, DP, Graphs | — | |
| YS | Max Indep Set | Difícil | Bitmasks, Meet in Middle, DP | Solución | |
| Gold | Lights Off | Difícil | Bitwise, DP | — | |
| IZhO | 2017 - Longest beautiful sequence | Difícil | Bitmasks, DP | Solución | |
| Gold | Redistributing Gifts | Difícil | Bitmasks, DP | — | |
| COCI | ★ 2016 - Burza | Muy difícil | Bitmasks, DP, Tree, Game Theory | Solución | |
| CEOI | 2019 - Amusement Park | Muy difícil | Bitmasks, DP | Solución | |
| IOI | ★ 2007 - Training | Muy difícil | Bitmasks, DP, Tree, DFS | — |
Aplicación: máscaras de bits sobre primos
Idea general
En algunos problemas de teoría de números, ayuda representar cada número con una máscara de bits de sus divisores primos. Por ejemplo, el conjunto se puede representar por (en binario)El prefijo simplemente indica que el número es binario, donde los bits corresponden a divisibilidad por .
Entonces, estas son algunas operaciones equivalentes entre máscaras y estos enteros:
- AND bit a bit es GCD
- OR bit a bit es LCM
- Iterar sobre bits es iterar sobre divisores primos
- Iterar sobre submáscaras es iterar sobre divisores Elegir un conjunto con GCD es equivalente a elegir un conjunto de máscaras de bits cuyo AND da . Por ejemplo, vemos que no tiene GCD porque . En cambio, tiene GCD porque .
Problemas
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| CF | Make it One | Difícil | DP, Combinatorics | Solución | |
| CF | Professional Layer | Muy difícil | DP, Bitmasks, NT | Solución | |
| CF | Gold Experience | Insano | Bitmasks, NT, Binary Search | — | |
| CF | Nora's Toy Boxes | Insano | DP, Bitmasks, Combinatorics | — |