Flujo máximo - algoritmo de Dinic
El algoritmo de Dinic resuelve el problema de flujo máximo en . El problema de flujo máximo se define en este artículo Flujo máximo - Ford-Fulkerson y Edmonds-Karp. Este algoritmo fue descubierto por Yefim Dinitz en 1970.
Definiciones
Una red residual de una red es una red que contiene dos aristas por cada arista :
- con capacidad
- con capacidad
Un flujo bloqueante (blocking flow) de alguna red es un flujo tal que todo camino de a contiene al menos una arista saturada por este flujo. Nótese que un flujo bloqueante no es necesariamente máximo.
Una red por capas (layered network) de una red es una red construida de la siguiente forma. Primero, para cada vértice calculamos : el camino más corto (sin pesos) de a este vértice usando solo aristas con capacidad positiva. Luego conservamos solo aquellas aristas para las que . Obviamente, esta red es acíclica.
Algoritmo
El algoritmo consiste en varias fases. En cada fase construimos la red por capas de la red residual de . Luego encontramos un flujo bloqueante arbitrario en la red por capas y lo sumamos al flujo actual.
Demostración de corrección
Mostremos que si el algoritmo termina, encuentra el flujo máximo.
Si el algoritmo terminó, no pudo encontrar un flujo bloqueante en la red por capas. Eso significa que la red por capas no tiene ningún camino de a . Eso significa que la red residual no tiene ningún camino de a . Eso significa que el flujo es máximo.
Número de fases
El algoritmo termina en menos de fases. Para demostrarlo, primero hay que probar dos lemas.
Lema 1. Las distancias de a cada vértice no disminuyen después de cada iteración, es decir, .
Demostración. Fijemos una fase y un vértice . Consideremos cualquier camino más corto de a en . La longitud de es igual a . Nótese que solo puede contener aristas de y aristas inversas de aristas de . Si no tiene aristas inversas respecto de , entonces porque también es un camino en . Ahora, supongamos que tiene al menos una arista inversa. Sea la primera de esas aristas . Entonces (por el primer caso). La arista no pertenece a , así que la arista fue afectada por el flujo bloqueante en la iteración anterior. Eso significa que . Además, . De estas dos ecuaciones y obtenemos . Ahora podemos usar la misma idea para el resto del camino.
Lema 2.
Demostración. Por el lema anterior, . Supongamos que . Nótese que solo puede contener aristas de y aristas inversas de aristas de . Eso significa que hay un camino más corto en que no fue bloqueado por el flujo bloqueante. Es una contradicción.
De estos dos lemas concluimos que hay menos de fases porque aumenta, pero no puede ser mayor que .
Encontrar un flujo bloqueante
Para encontrar el flujo bloqueante en cada iteración, podemos simplemente intentar empujar flujo con DFS de a en la red por capas mientras se pueda empujar. Para hacerlo más rápido, hay que eliminar las aristas que ya no se pueden usar para empujar. Para ello podemos mantener un puntero en cada vértice que apunta a la siguiente arista que se puede usar.
Una sola ejecución de DFS toma tiempo , donde es el número de avances de puntero en esta ejecución. Sumado sobre todas las ejecuciones, el número de avances de puntero no puede exceder . Por otro lado, el número total de ejecuciones no excederá , ya que cada ejecución satura al menos una arista. De esta forma, el tiempo de ejecución total de encontrar un flujo bloqueante es .
Complejidad
Hay menos de fases, así que la complejidad total es .
Redes unitarias
Una red unitaria (unit network) es una red en la que para cualquier vértice excepto y o bien la arista entrante o bien la saliente es única y tiene capacidad unitaria. Ese es exactamente el caso de la red que construimos para resolver el problema de matching máximo con flujos.
En redes unitarias el algoritmo de Dinic funciona en . Demostrémoslo.
Primero, cada fase ahora funciona en porque cada arista se considerará a lo sumo una vez.
Segundo, supongamos que ya ha habido fases. Entonces se han encontrado todos los caminos aumentantes con longitud . Sea el flujo actual, el flujo máximo. Consideremos su diferencia . Es un flujo en de valor y en cada arista es o . Se puede descomponer en caminos de a y posiblemente ciclos. Como la red es unitaria, no pueden tener vértices en común, así que el número total de vértices es , pero también es , así que en otras iteraciones encontraremos definitivamente el flujo máximo.
Redes de capacidades unitarias
En un escenario más genérico, cuando todas las aristas tienen capacidades unitarias, pero el número de aristas entrantes y salientes no está acotado, los caminos no pueden tener aristas en común en lugar de vértices en común. De forma similar esto permite demostrar la cota de sobre el número de iteraciones, de modo que el tiempo de ejecución del algoritmo de Dinic en tales redes es a lo sumo .
Finalmente, también es posible demostrar que el número de fases en redes de capacidad unitaria no excede , lo que da una estimación alternativa de en las redes con un número particularmente grande de aristas.
Implementación
struct FlowEdge {
int v, u;
long long cap, flow = 0;
FlowEdge(int v, int u, long long cap) : v(v), u(u), cap(cap) {}
};
struct Dinic {
const long long flow_inf = 1e18;
vector<FlowEdge> edges;
vector<vector<int>> adj;
int n, m = 0;
int s, t;
vector<int> level, ptr;
queue<int> q;
Dinic(int n, int s, int t) : n(n), s(s), t(t) {
adj.resize(n);
level.resize(n);
ptr.resize(n);
}
void add_edge(int v, int u, long long cap) {
edges.emplace_back(v, u, cap);
edges.emplace_back(u, v, 0);
adj[v].push_back(m);
adj[u].push_back(m + 1);
m += 2;
}
bool bfs() {
while (!q.empty()) {
int v = q.front();
q.pop();
for (int id : adj[v]) {
if (edges[id].cap == edges[id].flow)
continue;
if (level[edges[id].u] != -1)
continue;
level[edges[id].u] = level[v] + 1;
q.push(edges[id].u);
}
}
return level[t] != -1;
}
long long dfs(int v, long long pushed) {
if (pushed == 0)
return 0;
if (v == t)
return pushed;
for (int& cid = ptr[v]; cid < (int)adj[v].size(); cid++) {
int id = adj[v][cid];
int u = edges[id].u;
if (level[v] + 1 != level[u])
continue;
long long tr = dfs(u, min(pushed, edges[id].cap - edges[id].flow));
if (tr == 0)
continue;
edges[id].flow += tr;
edges[id ^ 1].flow -= tr;
return tr;
}
return 0;
}
long long flow() {
long long f = 0;
while (true) {
fill(level.begin(), level.end(), -1);
level[s] = 0;
q.push(s);
if (!bfs())
break;
fill(ptr.begin(), ptr.end(), 0);
while (long long pushed = dfs(s, flow_inf)) {
f += pushed;
}
}
return f;
}
};