Isola solitaria#9044

Isola solitaria (Editoriale)

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 :

prob[v] = \sum_{u \to v} prob[u] / OutDegree[u]

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:

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-9

Memorizzare 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);