El orden topológico lexicográficamente menor se construye con el algoritmo de Kahn con una cola de prioridad: en cada paso, entre todos los vértices disponibles (vértices en los que no entra ninguna arista proveniente de vértices aún no escritos), se elige el vértice con el número más pequeño.
Una arista añadida u \to v hace que el vértice no esté disponible hasta que se escriba el vértice . Así, añadiendo aristas podemos "esconder" vértices pequeños y obligar de este modo al algoritmo a elegir vértices con números mayores. Cada vértice escondido cuesta una arista del presupuesto .
La idea voraz. Construimos la respuesta posición a posición. La comparación lexicográfica se decide en la primera posición en que las secuencias difieren, por lo que en cada posición debemos colocar el vértice con el número mayor posible — ninguna ganancia en posiciones posteriores puede compensar una pérdida en la posición actual.
Dividimos los vértices que pueden escribirse en el paso actual en dos conjuntos:
— los vértices disponibles: todas las aristas que entran en ellos ya han sido "eliminadas". Si no hacemos nada, el algoritmo de Kahn elige el vértice más pequeño de ;
— los vértices aplazados. Son vértices que estaban disponibles, pero que decidimos esconder. Para cada uno de ellos nos comprometemos a añadir una arista que entre en él. El origen de esta arista lo elegiremos más tarde — será el vértice escrito inmediatamente antes del aplazado.
El conjunto se guarda en una cola de prioridad con el elemento más pequeño en la cima (un min-heap), y el conjunto , en una cola de prioridad con el elemento más grande en la cima (un max-heap).
Cuándo conviene aplazar un vértice. Sea el vértice más pequeño de . Si no lo tocamos, en la posición actual se colocará . Aplazar (gastando una arista) tiene sentido en dos casos:
contiene al menos dos vértices. Entonces, tras aplazar , en la posición actual se colocará el siguiente vértice de en orden creciente, que es mayor que ;
contiene solo el vértice , pero contiene un vértice aplazado y > x. Entonces, tras aplazar , el conjunto queda vacío, y en la posición actual se colocará el mayor vértice aplazado — un número mayor que .
En todos los demás casos aplazar no aumenta el número en la posición actual, y no hace falta gastar una arista. Aplazamos los vértices de uno en uno, empezando por el más pequeño, mientras convenga y mientras no se agote el presupuesto .
Qué vértice escribir. Después de los aplazamientos:
si no está vacío, escribimos el vértice más pequeño de — así lo exige la regla del orden lexicográficamente menor;
si está vacío, escribimos el vértice aplazado más grande de y añadimos una arista desde el último vértice escrito hasta . Es precisamente esta arista la que "escondía" el vértice : hasta ahora no estaba disponible, y ahora, cuando su nuevo predecesor ya ha sido escrito, se convierte en el único vértice disponible. Los demás vértices aplazados siguen escondidos — sus aristas se añadirán más tarde.
Después, como es habitual en el algoritmo de Kahn, disminuimos los grados de entrada de los vecinos del vértice escrito; los vértices cuyo grado de entrada pasa a ser cero se añaden a .
Por qué la respuesta es correcta. Todo vértice aplazado tarde o temprano se escribe desde , y en ese momento se añade exactamente una arista. Por lo tanto, el número de aristas añadidas es igual al número de aplazamientos y no supera . Todas las aristas añadidas van de un vértice escrito antes a un vértice escrito después, así que el orden hallado es un orden topológico del nuevo grafo y no aparecen ciclos.
Complejidad. Cada vértice entra en como máximo una vez y en como máximo una vez, y cada arista del grafo se examina una vez. La complejidad total es O((n + m) \log n).
Algoritmo de resolución del problema
Usamos el algoritmo de Kahn y dos colas de prioridad:
— min-heap que guarda los vértices disponibles, con el vértice de menor número en la cima.
— max-heap que guarda los vértices aplazados, con el vértice de mayor número en la cima.
Declaramos los siguientes arrays:
indeg[v] — el número de aristas entrantes del vértice que aún no se han procesado;
— el orden topológico resultante;
— la lista de aristas añadidas.
Paso 1. Inicialización
Calcular el grado de entrada de cada vértice.
Añadir a todos los vértices con grado de entrada cero.
Inicializar y la lista como vacíos.
Paso 2. Construcción de la respuesta
Mientras no se hayan procesado los vértices:
1. Aplazar un vértice si conviene.
Mientras el número de aristas añadidas sea menor que , el heap no esté vacío y se cumpla al menos una de las siguientes condiciones:
contiene al menos dos vértices;
contiene un único vértice , y contiene un vértice tal que y > x.
Extraer el vértice más pequeño de e insertarlo en . Este vértice se procesará más tarde.
2. Elegir el siguiente vértice.
Si no está vacío, extraer de él el vértice más pequeño .
En caso contrario, extraer el vértice más grande de . Si no está vacío, añadir una arista desde el último vértice de hasta .
Añadir al final de .
3. Actualizar el grafo.
Para cada arista saliente v \to u:
Disminuir indeg[u] en uno.
Si indeg[u] pasa a ser cero, añadir a .
Repetir estos pasos hasta que todos los vértices se hayan añadido a .
Paso 3. Salida
Imprimir la secuencia .
Imprimir el número de aristas añadidas.
Imprimir las propias aristas añadidas.
Ejemplo
Consideremos el ejemplo del enunciado: n = 5, k = 2, aristas 1 \to 4, 4 \to 2, 1 \to 3. Grados de entrada iniciales: los vértices y tienen grado de entrada , los vértices tienen grado de entrada . Los conjuntos iniciales son A = \{1, 5\}, B = \{\,\}.
Paso | Aplazados | Se toma | Arista | Orden | |||
(dos vértices en ) | de | — | |||||
— | de | 5 \to 1 | |||||
(dos vértices en : y ) | de | — | |||||
— (k = 0) | de | — | 5, 1, 4, 2 | ||||
— | de | 2 \to 3 | 5, 1, 4, 2, 3 |
Veamos los pasos con más detalle.
Paso 1. A = \{1, 5\} contiene dos vértices, así que aplazamos el más pequeño — . Ahora A = \{5\} contiene un vértice, y no tiene vértices mayores que , así que dejamos de aplazar. Escribimos .
Paso 2. está vacío. Escribimos el mayor vértice aplazado y añadimos la arista 5 \to 1. Eliminamos las aristas 1 \to 4 y 1 \to 3: los vértices y pasan a estar disponibles, A = \{3, 4\}.
Paso 3. contiene dos vértices — aplazamos , y el presupuesto de aristas se agota (k = 0). Escribimos , tras lo cual se elimina la arista 4 \to 2 y A = \{2\}.
Paso 4. No son posibles más aplazamientos. Escribimos .
Paso 5. está vacío. Escribimos el vértice aplazado y añadimos la arista 2 \to 3.
Obtenemos el orden 5, 1, 4, 2, 3 y las aristas añadidas 5 \to 1 y 2 \to 3. El grafo original (a la izquierda) y el grafo con las aristas añadidas (a la derecha, las aristas añadidas se muestran en rojo):
Sin aristas añadidas, el orden lexicográficamente menor es 1, 3, 4, 2, 5: el vértice no está conectado con nada, pero por su número grande se elige el último. Tras añadir dos aristas, el vértice pasa a ser el primero, y el orden 5, 1, 4, 2, 3 es lexicográficamente mayor.
Consideremos un ejemplo más, en el que al principio hay cuatro vértices disponibles. Sean n = 8, m = 6, k = 4, y sean las aristas del grafo 6 \to 3, 4 \to 8, 3 \to 5, 1 \to 7, 2 \to 7, 5 \to 7.
En los vértices 1, 2, 4, 6 no entra ninguna arista, así que al principio A = \{1, 2, 4, 6\}, B = \{\,\}. Sin aristas añadidas, el orden lexicográficamente menor es 1, 2, 4, 6, 3, 5, 7, 8.
El grafo original (a la izquierda) y el grafo tras añadir aristas (a la derecha, las aristas añadidas se muestran en rojo)
Paso | antes | antes | Aplazados | Se toma | Arista | |
\{1, 2, 4, 6\} | de | — | ||||
\{4, 2, 1\} | 3~(4 > 3) | de | 6 \to 4 | |||
\{3, 2, 1\} | — | de | — | |||
\{3, 2, 1\} | — | de | 8 \to 3 | |||
— | de | — | ||||
— | de | 5 \to 2 | ||||
— | de | 2 \to 1 | ||||
— | de | — |
Veamos los pasos con más detalle.
Paso 1. A = \{1, 2, 4, 6\}, k = 4. Aplazamos el vértice , ya que contiene cuatro vértices, luego (tres vértices en ), luego (dos vértices en ). El presupuesto baja a k = 1. Ahora A = \{6\} contiene un vértice, y el mayor vértice aplazado es menor que — seguir aplazando no conviene. Escribimos . Se elimina la arista 6 \to 3, y A = \{3\}.
Paso 2. A = \{3\}, B = \{4, 2, 1\}, k = 1. contiene un vértice, pero contiene el vértice 4 > 3, así que aplazamos también ; el presupuesto se agota, k = 0. está vacío — escribimos el mayor vértice aplazado y añadimos la arista 6 \to 4. Se elimina la arista 4 \to 8, y A = \{8\}.
Paso 3. No son posibles más aplazamientos. Escribimos , tras lo cual queda vacío.
Paso 4. Escribimos el mayor vértice aplazado y añadimos la arista 8 \to 3. Se elimina la arista 3 \to 5, y A = \{5\}.
Paso 5. Escribimos . Se elimina la arista 5 \to 7, pero en el vértice aún entran aristas desde y , así que está vacío.
Paso 6. Escribimos el vértice aplazado y añadimos la arista 5 \to 2. Se elimina la arista 2 \to 7.
Paso 7. Escribimos el vértice aplazado y añadimos la arista 2 \to 1. Se elimina la arista 1 \to 7, y A = \{7\}.
Paso 8. Escribimos .
La respuesta:
6 4 8 3 5 2 1 7
4
6 4
8 3
5 2
2 1Este ejemplo muestra claramente todos los casos del algoritmo. En el paso se aplazan tres vértices de una vez — mientras contenga al menos dos vértices. En el paso se aplica la segunda condición: contiene solo el vértice , pero contiene el vértice mayor , así que también se aplaza. Los vértices aplazados salen de en orden decreciente (4, 3, 2, 1), y cada uno de ellos recibe exactamente una arista desde el vértice escrito antes que él. Por tanto, se añaden exactamente tantas aristas como aplazamientos hubo — cuatro.
Implementación del algoritmo
Leemos los datos de entrada. Construimos el grafo. Las listas de adyacencia se guardan en , y los grados de entrada, en .
scanf("%d %d %d", &n, &m, &k);
vector<vector<int>> g(n + 1);
vector<int> indeg(n + 1, 0);
for (int i = 0; i < m; i++)
{
scanf("%d %d", &u, &v);
g[u].push_back(v);
indeg[v]++;
}Declaramos dos colas de prioridad: — los vértices disponibles, con el elemento más pequeño en la cima (un min-heap); — los vértices aplazados, con el elemento más grande en la cima (un max-heap).
priority_queue<int, vector<int>, greater<int>> A; // disponibles, min-heap
priority_queue<int> B; // aplazados, max-heapAl principio están disponibles todos los vértices con grado de entrada cero.
for (int v = 1; v <= n; v++)
if (indeg[v] == 0) A.push(v);El array acumula la respuesta — el orden topológico buscado, y el array guarda las aristas añadidas.
vector<int> order;
order.reserve(n);
vector<pair<int, int>> added;Cada iteración del bucle añade un vértice a la respuesta, hasta que se hayan escrito los vértices.
while ((int)order.size() < n)
{Aplazamos vértices. Mientras queden aristas en el presupuesto y el conjunto no esté vacío, consideramos el menor vértice disponible . Conviene aplazarlo si contiene al menos un vértice más o si contiene un vértice aplazado con un número mayor. El vértice aplazado se traslada de a , y el presupuesto disminuye en . En caso contrario, los aplazamientos se detienen.
// aplazamos vertices mientras convenga y queden aristas
while (k > 0 && !A.empty())
{
int x = A.top();
if (A.size() >= 2 || (!B.empty() && B.top() > x))
{
A.pop();
B.push(x);
k--;
}
else break;
}Elegimos el vértice que se escribirá en la posición actual. Si no está vacío, es el menor vértice disponible. En caso contrario, tomamos el mayor vértice aplazado y añadimos una arista desde el último vértice escrito order.back() hasta .
int v;
if (!A.empty())
{
v = A.top();
A.pop();
}
else
{
v = B.top();
B.pop();
added.push_back(make_pair(order.back(), v));
}Añadimos a la respuesta y, como en el algoritmo de Kahn, disminuimos los grados de entrada de sus vecinos. Los vértices cuyo grado de entrada pasa a ser cero quedan disponibles.
order.push_back(v);
for (int to : g[v])
if (--indeg[to] == 0) A.push(to);
}Imprimimos el orden topológico hallado, el número de aristas añadidas y las propias aristas.
for (int i = 0; i < n; i++)
printf("%d ", order[i]);
printf("\n%d\n", added.size());
for (auto &e : added)
printf("%d %d\n", e.first, e.second);