Per l'enunciato del problema, una volta lasciata un'isola non è mai più possibile tornarci. Quindi il grafo i cui vertici sono le isole e i cui archi sono i ponti è un grafo diretto aciclico. Ci si può bloccare solo su un'isola priva di ponti in uscita, cioè in un vertice senza uscita con grado uscente zero: OutDegree[v] = 0.
Sia prob[v] la probabilità che durante il percorso si raggiunga il vertice . Per il vertice di partenza prob[r] = 1; per tutti gli altri vertici inizialmente prob[v] = 0. Poiché il grafo non contiene cicli, ogni vertice può essere visitato al massimo una volta.
Supponiamo che da un vertice escano OutDegree[u] archi. Trovandoci in , ne scegliamo uno uniformemente a caso, quindi ci spostiamo lungo ciascun arco u \to v con probabilità prob[u] / OutDegree[u]. Il vertice può essere raggiunto solo dai suoi predecessori diretti. Pertanto prob[v] è uguale alla somma dei contributi di tutti gli archi u \to v entranti in :
Il valore prob[v] può essere calcolato solo quando le probabilità di tutti i suoi predecessori sono definitive. Questo è esattamente l'ordine di elaborazione fornito da un ordinamento topologico: ogni vertice compare dopo tutti i vertici da cui partono archi verso di esso. Pertanto costruiamo l'ordine topologico tramite l'algoritmo di Kahn e contemporaneamente "spingiamo" le probabilità lungo gli archi.
L'algoritmo è il seguente:
per ogni vertice , calcolare il suo grado entrante InDegree[v] e il suo grado uscente OutDegree[v];
inserire nella coda tutti i vertici con grado entrante zero e impostare prob[r] = 1;
finché la coda non è vuota, estrarre da essa un vertice . A questo punto tutti i suoi predecessori sono già stati elaborati, quindi il valore prob[v] è definitivo. Per ogni arco v \to to, aggiungere prob[v] / OutDegree[v] a prob[to] e diminuire InDegree[to] di . Se il grado entrante del vertice diventa zero, inserirlo nella coda.
Inizialmente, la coda contiene tutti i vertici con grado entrante zero, non solo quello di partenza. Vengono elaborati anche i vertici non raggiungibili da , ma la loro probabilità rimane zero e non influisce sulla risposta.
Ricerca della risposta. Tra i vertici senza uscita, trovare la probabilità massima . Quindi stampare, in ordine crescente, gli indici di tutti i vertici senza uscita tali che |prob[v] - mx| \le 10^{-9}. Il confronto con una tolleranza è necessario poiché le probabilità vengono calcolate con numeri in virgola mobile soggetti a errori di arrotondamento.
Complessità. Ogni vertice viene inserito ed estratto dalla coda esattamente una volta, e ogni arco viene esaminato una volta. Il tempo di esecuzione dell'algoritmo è O(n + m).
Esempio
Simuliamo l'algoritmo sul grafo dell'esempio. Accanto a ogni vertice è indicata la probabilità prob[v] di raggiungerlo. I vertici elaborati sono ombreggiati in grigio, gli archi lungo i quali la probabilità viene distribuita al passo corrente sono evidenziati in blu, e gli archi già esaminati sono disegnati tratteggiati.
Inizialmente (figura a sinistra) prob[1] = 1, e le probabilità di tutti gli altri vertici sono . Il vertice è l'unico vertice con grado entrante zero, quindi è il primo a essere estratto dalla coda.
Dal vertice escono quattro archi, quindi la probabilità prob[1] = 1 viene suddivisa equamente tra i vertici e : ciascuno di essi riceve 1 / 4 = 0.25 (figura a destra). Dopo aver rimosso gli archi uscenti dal vertice , i gradi entranti dei vertici e diventano zero, e vengono inseriti nella coda.
Successivamente viene elaborato il vertice . Da esso escono due archi, quindi prob[2] / 2 = 0.25 / 2 = 0.125 viene aggiunto a e . Otteniamo prob[4] = prob[5] = 0.375 (figura in basso a sinistra). Il grado entrante del vertice diventa zero, e viene inserito nella coda.
Viene elaborato poi il vertice . Da esso esce un singolo arco 3 \to 4, quindi aumenta di prob[3] / 1 = 0.25 e diventa (figura in basso a destra). Ora anche il vertice viene inserito nella coda.
I vertici e non hanno archi uscenti, quindi la loro elaborazione non modifica alcuna probabilità. L'ordine topologico risultante è 1, 2, 3, 5, 4.
I vertici senza uscita sono e , con probabilità rispettivamente e . La loro somma è uguale a : siamo certi di bloccarci da qualche parte. La probabilità massima è raggiunta dal singolo vertice , che è la risposta.
Implementazione dell'algoritmo
Dichiarare la costante — la tolleranza per il confronto di numeri in virgola mobile.
#define EPS 1e-9Memorizzare il grafo di input come lista di adiacenza . Memorizzare i gradi entranti e uscenti dei vertici negli array InDegree e OutDegree. La coda viene utilizzata nell'algoritmo di Kahn. In prob[v] calcoliamo la probabilità di raggiungere il vertice .
vector\u003cvector\u003cint\u003e\u003e g;
vector\u003cint\u003e InDegree, OutDegree;
deque\u003cint\u003e q;
vector\u003cdouble\u003e prob;Leggere il numero di isole , il numero di ponti e l'indice dell'isola di partenza . Inizializzare la lista di adiacenza e gli array dei gradi.
scanf("%d %d %d", \u0026n, \u0026m, \u0026r);
g.assign(n + 1, vector\u003cint\u003e());
InDegree.assign(n + 1, 0);
OutDegree.assign(n + 1, 0);Leggere i ponti e costruire il grafo. Per ogni arco a \to b, aumentare il grado uscente del vertice e il grado entrante del vertice .
for (i = 0; i \u003c m; i++)
{
scanf("%d %d", \u0026a, \u0026b);
g[a].push_back(b);
OutDegree[a]++;
InDegree[b]++;
}Inserire nella coda tutti i vertici con grado entrante zero — l'ordinamento topologico inizia da essi.
for (i = 1; i \u003c InDegree.size(); i++)
if (InDegree[i] == 0) q.push_back(i);La probabilità di trovarsi nel vertice di partenza è . Per tutti gli altri vertici la probabilità iniziale è : la funzione riempie l'array con zeri.
prob.resize(n + 1);
prob[r] = 1;Eseguire l'algoritmo di Kahn. I vertici vengono estratti dalla coda in ordine topologico.
while (!q.empty())
{Estrarre un vertice dalla coda. A questo punto tutti i suoi predecessori sono già stati elaborati, quindi il valore prob[v] è definitivo. Scorrere gli archi (v, to). Dal vertice escono OutDegree[v] archi, e ciascuno di essi viene scelto con uguale probabilità. Pertanto ci spostiamo lungo l'arco v \to to con probabilità prob[v] / OutDegree[v]. Aggiungere questo valore a prob[to].
v = q.front(); q.pop_front();
for (int to : g[v])
{
prob[to] += prob[v] / OutDegree[v];Rimuovere l'arco (v, to) dal grafo — diminuire il grado entrante del vertice .
InDegree[to]--;Se il grado entrante del vertice diventa zero, tutti i suoi predecessori sono stati elaborati e il valore prob[to] è definitivo. Inserire nella coda.
if (InDegree[to] == 0) q.push_back(to);
}
}Trovare la probabilità massima tra i vertici senza uscita — le isole prive di ponti in uscita (OutDegree[i] = 0).
mx = 0;
for (i = 1; i \u003c= n; i++)
if (OutDegree[i] == 0 \u0026\u0026 prob[i] \u003e mx) mx = max(mx, prob[i]);Stampare, in ordine crescente, gli indici di tutti i vertici senza uscita la cui probabilità differisce da al massimo di . Potrebbero esserci più vertici di questo tipo.
for (i = 1; i \u003c= n; i++)
if (OutDegree[i] == 0 \u0026\u0026 fabs(prob[i] - mx) \u003c= EPS)
printf("%d ", i);