Según el enunciado del problema, una vez que abandonas una isla nunca puedes regresar a ella. Por lo tanto, el grafo cuyos vértices son las islas y cuyas aristas son los puentes es un grafo acíclico dirigido. Solo puedes quedarte atascado en una isla sin puentes salientes, es decir, en un vértice sin salida con grado de salida cero: OutDegree[v] = 0.
Sea prob[v] la probabilidad de que durante el recorrido lleguemos al vértice . Para el vértice inicial prob[r] = 1; para todos los demás vértices inicialmente prob[v] = 0. Dado que el grafo no contiene ciclos, cada vértice puede visitarse como máximo una vez.
Supongamos que del vértice salen OutDegree[u] aristas. Estando en , elegimos una de ellas de manera uniformemente aleatoria, de modo que nos movemos por cada arista u \to v con probabilidad prob[u] / OutDegree[u]. Al vértice solo se puede llegar desde sus predecesores directos. Por lo tanto, prob[v] es igual a la suma de las contribuciones de todas las aristas u \to v que entran en :
El valor prob[v] solo puede calcularse cuando las probabilidades de todos sus predecesores son definitivas. Este es exactamente el orden de procesamiento que proporciona un orden topológico: cada vértice aparece después de todos los vértices desde los cuales llegan aristas hacia él. Por lo tanto, construimos el orden topológico usando el algoritmo de Kahn y, al mismo tiempo, "empujamos" las probabilidades a lo largo de las aristas.
El algoritmo es el siguiente:
para cada vértice , calcular su grado de entrada InDegree[v] y grado de salida OutDegree[v];
colocar en la cola todos los vértices con grado de entrada cero y establecer prob[r] = 1;
mientras la cola no esté vacía, extraer un vértice de ella. En este momento, todos sus predecesores ya han sido procesados, por lo que el valor prob[v] es definitivo. Para cada arista v \to to, sumar prob[v] / OutDegree[v] a prob[to] y disminuir InDegree[to] en . Si el grado de entrada del vértice se vuelve cero, colocarlo en la cola.
Inicialmente, la cola contiene todos los vértices con grado de entrada cero, no solo el vértice inicial. Los vértices inalcanzables desde también se procesan, pero su probabilidad permanece en cero y no afecta la respuesta.
Encontrar la respuesta. Entre los vértices sin salida, encontrar la probabilidad máxima . Luego imprimir, en orden creciente, los índices de todos los vértices sin salida tales que |prob[v] - mx| \le 10^{-9}. La comparación con tolerancia es necesaria porque las probabilidades se calculan con números de punto flotante con errores de redondeo.
Complejidad. Cada vértice se coloca y se extrae de la cola exactamente una vez, y cada arista se examina una vez. El tiempo de ejecución del algoritmo es O(n + m).
Ejemplo
Simulemos el algoritmo sobre el grafo del ejemplo. Junto a cada vértice se muestra la probabilidad prob[v] de alcanzarlo. Los vértices procesados están sombreados en gris, las aristas a lo largo de las cuales se distribuye la probabilidad en el paso actual están resaltadas en azul, y las aristas ya examinadas se dibujan con línea discontinua.
Inicialmente (figura izquierda) prob[1] = 1, y las probabilidades de todos los demás vértices son . El vértice es el único vértice con grado de entrada cero, por lo que es el primero en extraerse de la cola.
Del vértice salen cuatro aristas, por lo que la probabilidad prob[1] = 1 se divide equitativamente entre los vértices y : cada uno de ellos recibe 1 / 4 = 0.25 (figura derecha). Tras eliminar las aristas que salen del vértice , los grados de entrada de los vértices y se vuelven cero, y se colocan en la cola.
A continuación se procesa el vértice . De él salen dos aristas, por lo que se suma prob[2] / 2 = 0.25 / 2 = 0.125 a y . Obtenemos prob[4] = prob[5] = 0.375 (figura inferior izquierda). El grado de entrada del vértice se vuelve cero, y se coloca en la cola.
A continuación se procesa el vértice . De él sale una única arista 3 \to 4, por lo que aumenta en prob[3] / 1 = 0.25 y pasa a ser (figura inferior derecha). Ahora el vértice también se coloca en la cola.
Los vértices y no tienen aristas salientes, por lo que procesarlos no cambia ninguna probabilidad. El orden topológico resultante es 1, 2, 3, 5, 4.
Los vértices sin salida son y , con probabilidades y respectivamente. Su suma es igual a : tenemos la certeza de quedarnos atascados en algún lugar. La probabilidad máxima se alcanza en el único vértice , que es la respuesta.
Implementación del algoritmo
Declarar la constante — la tolerancia para comparar números de punto flotante.
#define EPS 1e-9Almacenar el grafo de entrada como una lista de adyacencia . Almacenar los grados de entrada y de salida de los vértices en los arreglos InDegree y OutDegree. La cola se utiliza en el algoritmo de Kahn. En prob[v] calculamos la probabilidad de alcanzar el vértice .
vector<vector<int>> g;
vector<int> InDegree, OutDegree;
deque<int> q;
vector<double> prob;Leer el número de islas , el número de puentes y el índice de la isla inicial . Inicializar la lista de adyacencia y los arreglos de grados.
scanf("%d %d %d", &n, &m, &r);
g.assign(n + 1, vector<int>());
InDegree.assign(n + 1, 0);
OutDegree.assign(n + 1, 0);Leer los puentes y construir el grafo. Para cada arista a \to b, aumentar el grado de salida del vértice y el grado de entrada del vértice .
for (i = 0; i < m; i++)
{
scanf("%d %d", &a, &b);
g[a].push_back(b);
OutDegree[a]++;
InDegree[b]++;
}Colocar en la cola todos los vértices con grado de entrada cero — el orden topológico comienza con ellos.
for (i = 1; i < InDegree.size(); i++)
if (InDegree[i] == 0) q.push_back(i);La probabilidad de estar en el vértice inicial es . Para todos los demás vértices la probabilidad inicial es : la función llena el arreglo con ceros.
prob.resize(n + 1);
prob[r] = 1;Ejecutar el algoritmo de Kahn. Los vértices se extraen de la cola en orden topológico.
while (!q.empty())
{Extraer un vértice de la cola. En este momento, todos sus predecesores ya han sido procesados, por lo que el valor prob[v] es definitivo. Recorrer las aristas (v, to). De vértice salen OutDegree[v] aristas, y cada una de ellas se elige con igual probabilidad. Por lo tanto, nos movemos por la arista v \to to con probabilidad prob[v] / OutDegree[v]. Sumar este valor a prob[to].
v = q.front(); q.pop_front();
for (int to : g[v])
{
prob[to] += prob[v] / OutDegree[v];Eliminar la arista (v, to) del grafo — disminuir el grado de entrada del vértice .
InDegree[to]--;Si el grado de entrada del vértice se vuelve cero, todos sus predecesores han sido procesados y el valor prob[to] es definitivo. Colocar en la cola.
if (InDegree[to] == 0) q.push_back(to);
}
}Encontrar la probabilidad máxima entre los vértices sin salida — las islas sin puentes salientes (OutDegree[i] = 0).
mx = 0;
for (i = 1; i <= n; i++)
if (OutDegree[i] == 0 && prob[i] > mx) mx = max(mx, prob[i]);Imprimir, en orden creciente, los índices de todos los vértices sin salida cuya probabilidad difiere de en, como máximo, . Puede haber varios de estos vértices.
for (i = 1; i <= n; i++)
if (OutDegree[i] == 0 && fabs(prob[i] - mx) <= EPS)
printf("%d ", i);