Corte mínimo
Recursos
Los recursos de abajo incluyen muchas aplicaciones ingeniosas del corte mínimo, incluido el problema de cierre (closure problem).
| Fuente | Recurso | Notas |
|---|---|---|
| CPC | 10 - Network Flow | Diapositivas de “Algorithm Design”. Teorema del corte mínimo y flujo máximo, aplicaciones de flujo / corte mínimo. |
Teorema del corte mínimo y flujo máximo
Dada una red de flujo con fuente y sumidero , un corte - es una partición de los vértices en dos conjuntos y con y . Su capacidad es la capacidad total de las aristas que cruzan del lado de la fuente al lado del sumidero:
(Las aristas que apuntan al otro lado, de a , no contribuyen.) Cortar todas estas aristas desconecta de , así que intuitivamente la capacidad de un corte es una cota superior de cuánto flujo puede ir de a . En efecto, para cualquier flujo y cualquier corte ,
donde es el flujo total de a . Así, todo flujo está acotado por todo corte.

Aquí hay una ilustración de un corte -: los nodos rojos están en y los azules en . Es importante que y no necesitan ser conexos: el único requisito es que y . La capacidad de este corte es la suma de las capacidades subrayadas en verde, . El flujo a través de este corte se obtiene de los números subrayados en amarillo, , que coincide con el flujo - real, . Nótese también que en esta instancia el flujo a través del corte () es menor que su capacidad (). Sin embargo, si elegimos y , el flujo a través del corte () y su capacidad () serán iguales.
El teorema del corte mínimo y flujo máximo afirma que, en general, siempre podemos alcanzar esa igualdad:
El valor máximo de un flujo - es igual a la capacidad mínima de un corte -.
Para mostrar que la cota es justa, solo hace falta construir un flujo y
un corte tales que .
Para ello, tomamos como cualquier flujo máximo y usamos el hecho de que
no puede tener caminos aumentantes restantes para construir un corte
de capacidad mínima. De hecho, este corte se puede construir
tomando como el conjunto de nodos alcanzables desde la fuente en el
grafo residual (y como el conjunto de todos los demás nodos, es decir,
). Para ver por qué, consideremos el siguiente diagrama:

Por definición, las aristas rojas deben estar saturadas hasta su capacidad; de lo contrario, los nodos de podrían alcanzar nodos de . De forma similar, las aristas azules deben tener flujo 0. Por lo tanto, el flujo a través de este corte es igual a la capacidad de este corte, como se deseaba.
Cubiertas mínimas de nodos
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| CSES | Coin Grid | Fácil | en el módulo |
| Fuente | Recurso | Notas |
|---|---|---|
| CPH | 20.3 - Maximum Matchings | menciones breves del teorema de Hall y del teorema de König |
Solución - Coin Grid
Este problema pide hallar una cubierta mínima de nodos de un grafo bipartito. Construimos un grafo de flujo con vértices etiquetados , fuente , sumidero , y las siguientes aristas:
- Aristas de con capacidad para cada . Cortar la -ésima de esas aristas corresponde a elegir la -ésima fila.
- Aristas de con capacidad para cada . Cortar la -ésima de esas aristas corresponde a elegir la -ésima columna.
- Si existe una moneda en , agregamos una arista de con capacidad .
Primero hallamos un flujo máximo, que nos dice cuántas aristas de capacidad
1 hay que cortar. Para hallar el corte mínimo en sí, hacemos BFS otra vez
desde la fuente. Las aristas que conectan vértices alcanzables
desde la fuente (lev[a] != -1) con vértices que no lo son
(lev[b] == -1) forman parte del corte mínimo. En este caso, cada una de
esas aristas debe ser de la forma o para
. Cada arista cortada corresponde a una fila o columna de la
que quitamos monedas.
Nótese que las aristas de la forma no se pueden cortar porque tienen capacidad .
struct Dinic { // flow template
using F = ll; // flow type
struct Edge {
int to;
F flo, cap;
};
int N;
V<Edge> eds;
V<vi> adj;
void init(int _N) {
N = _N;
adj.rsz(N), cur.rsz(N);
}
/// void reset() { trav(e,eds) e.flo = 0; }
void ae(int u, int v, F cap, F rcap = 0) {
assert(min(cap, rcap) >= 0);
adj[u].pb(sz(eds));
eds.pb({v, 0, cap});
adj[v].pb(sz(eds));
eds.pb({u, 0, rcap});
}
vi lev;
V<vi::iterator> cur;
bool bfs(int s, int t) { // level = shortest distance from source
lev = vi(N, -1);
F0R(i, N) cur[i] = begin(adj[i]);
queue<int> q({s});
lev[s] = 0;
while (sz(q)) {
int u = q.ft;
q.pop();
trav(e, adj[u]) {
const Edge &E = eds[e];
int v = E.to;
if (lev[v] < 0 && E.flo < E.cap) q.push(v), lev[v] = lev[u] + 1;
}
}
return lev[t] >= 0;
}
F dfs(int v, int t, F flo) {
if (v == t) return flo;
for (; cur[v] != end(adj[v]); cur[v]++) {
Edge &E = eds[*cur[v]];
if (lev[E.to] != lev[v] + 1 || E.flo == E.cap) continue;
F df = dfs(E.to, t, min(flo, E.cap - E.flo));
if (df) {
E.flo += df;
eds[*cur[v] ^ 1].flo -= df;
return df;
} // saturated >=1 one edge
}
return 0;
}
F maxFlow(int s, int t) {
F tot = 0;
while (bfs(s, t))
while (F df = dfs(s, t, numeric_limits<F>::max())) tot += df;
return tot;
}
};
int main() {
int n;
re(n);
Dinic D;
D.init(2 * n + 2);
F0R(i, n) {
D.ae(0, i + 1, 1);
D.ae(i + 1 + n, 2 * n + 1, 1);
F0R(j, n) {
char c;
re(c);
if (c == 'o') D.ae(i + 1, j + 1 + n, MOD); // some big capacity -> not cut
}
}
ps(D.maxFlow(0, 2 * n + 1));
D.bfs(0, 2 * n + 1);
FOR(i, 1, n + 1) if (D.lev[i] < 0) ps(1, i); // edge from 0 to i is cut
FOR(i, 1, n + 1)
if (D.lev[i + n] >= 0) ps(2, i); // edge from i+n to 2*n+1 is cut
}Cubiertas mínimas de caminos
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| Kattis | The Wrath of Khan | Difícil | en el módulo |
| Fuente | Recurso | Notas |
|---|---|---|
| CPH | 20.4 - Path Covers | menciones breves de cubiertas de caminos disjuntos en nodos y generales, teorema de Dilworth |
| Wikipedia | Dilworth's Theorem | demostración vía el teorema de König |
Solución - The Wrath of Kahn
Ignoramos todos los vértices de que nunca pueden formar parte de . Luego nuestro objetivo es hallar el tamaño de una anticadena máxima en el grafo restante, que como se menciona en CPH es simplemente una instancia de cubierta mínima de caminos.
TopoSort<500> T;
int n, m;
bool link[500][500];
vi out[500];
Dinic<1005> D;
int main() {
setIO();
re(n, m);
F0R(i, m) {
int x, y;
re(x, y);
T.ae(x, y);
link[x][y] = 1;
}
F0R(k, n) F0R(a, n) F0R(b, n) link[a][b] |= link[a][k] & link[k][b];
T.sort(n);
vi bad;
F0R(i, n) if (T.in[i]) bad.pb(i); // cannot be part of S
trav(a, bad) F0R(i, n) link[a][i] = link[i][a] = 0;
F0R(i, n) {
D.ae(2 * n, i, 1);
D.ae(i + n, 2 * n + 1, 1);
}
F0R(i, n) F0R(j, n) if (link[i][j]) D.ae(i, n + j, 1);
int chain = n - sz(bad) - D.maxFlow(2 * n + 2, 2 * n, 2 * n + 1);
ps(chain);
}Problemas
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| CSES | Police Chase | Fácil | Solución | ||
| Old Gold | Cow Steeplechase | Fácil | Max Flow | — | |
| CSA | Fashion | Normal | — | ||
| CF | Card Game | Normal | — | ||
| CF | Bricks | Normal | — | ||
| CF | Goods Transportation | Difícil | — | ||
| AC | ARC E - MUL | Difícil | — | ||
| FHC | Gentrification | Difícil | — |