Metropolitana di Baku#11865

Metropolitana di Baku (Editoriale)

Lo schema della metropolitana è rappresentato da un grafo non orientato connesso con vertici e archi. Un grafo connesso su vertici, contenente esattamente n - 1 archi, è un albero. Aggiungendo un ulteriore arco a un albero si crea esattamente un ciclo. Pertanto, il grafo descritto nella condizione è costituito da un unico ciclo (la linea circolare), ai cui vertici sono appesi degli alberi (eventualmente vuoti).

Per ogni vertice occorre trovare la lunghezza del cammino più breve verso il vertice più vicino del ciclo. Per i vertici del ciclo la risposta è pari a . Il problema si risolve in due fasi.

Fase 1. Individuazione del ciclo: rimozione sequenziale delle foglie

Calcoliamo i gradi di tutti i vertici. Inseriamo in una coda tutti i vertici di grado , cioè le foglie. Finché la coda non è vuota, estraiamo da essa un vertice e lo contrassegniamo come rimosso. Successivamente diminuiamo di il grado di ogni suo vicino non ancora rimosso. Se il grado di un vicino diventa pari a , allora esso stesso si è trasformato in una foglia, e lo aggiungiamo alla coda.

Al termine del processo rimarranno non rimossi solo i vertici del ciclo. Questo segue da due osservazioni:

Fase 2. Ricerca in ampiezza multi-sorgente

Poniamo d[v] = 0 per tutti i vertici del ciclo e d[v] = –1 (non visitato) per gli altri. Inseriamo tutti i vertici del ciclo nella coda contemporaneamente e avviamo da essi una ricerca in ampiezza. Quando un vertice viene estratto dalla coda, ogni suo vicino non visitato riceve il valore d[w] = d[u] + 1 e viene aggiunto alla coda.

La ricerca in ampiezza da un insieme di sorgenti elabora i vertici in ordine non decrescente di distanza da tale insieme. Pertanto è uguale al numero minimo di passaggi da alla linea circolare. Poiché tutto ciò che si trova al di fuori del ciclo è un albero, il percorso da ogni stazione fino all'anello è in realtà unico.

Questo processo ricorda un ordinamento topologico inverso: rimuoviamo sequenzialmente le foglie finché non rimangono solo i vertici "interni" — sono proprio questi a costituire il ciclo.

Successivamente avviamo una ricerca in ampiezza simultaneamente da tutti i vertici appartenenti al ciclo. In questo modo determiniamo, per ogni vertice rimanente, il numero minimo di passi (passaggi) fino al vertice più vicino dell'anello.

Esempio

Nel primo esempio tutti i vertici fanno parte di un unico ciclo, pertanto la distanza per ciascuno di essi è pari a .

Nel secondo esempio il ciclo è formato dai vertici e , mentre gli altri vertici si trovano a una certa distanza da esso.

Implementazione dell'algoritmo

Dichiariamo la lista di adiacenza del grafo , nonché gli array ausiliari:

La coda viene utilizzata in entrambe le fasi dell'algoritmo: prima per la rimozione sequenziale delle foglie, poi per la ricerca in ampiezza multi-sorgente.

vector<vector<int> > g;
vector<int> used, dist, deg;
queue<int> q;

Leggiamo i dati di input. Costruiamo il grafo.

scanf("%d", &n);
g.resize(n + 1);
for (i = 0; i < n; i++)
{
  scanf("%d %d", &a, &b);
  g[a].push_back(b);
  g[b].push_back(a);
}

Calcoliamo i gradi iniziali dei vertici: il grado del vertice è uguale alla dimensione della sua lista di adiacenza . Tutte le foglie, cioè i vertici di grado , le inseriamo nella coda e le contrassegniamo subito come rimosse (used[i] = 1).

used.resize(n + 1, 0);
deg.resize(n + 1, 0);
for (i = 1; i <= n; i++)
{
  deg[i] = g[i].size();
  if (deg[i] == 1)
  {
    q.push(i);
    used[i] = 1;
  }
}

Rimuoviamo sequenzialmente le foglie. Esternamente questo ciclo assomiglia a una ricerca in ampiezza, tuttavia un vertice entra nella coda non al primo incontro, ma solo quando esso stesso diventa una foglia. Estraiamo dalla coda il vertice e scorriamo i suoi vicini . I vicini già contrassegnati come rimossi vengono saltati. Per ogni vicino non ancora rimosso diminuiamo il grado di , poiché l'arco (v, to) scompare dal grafo insieme al vertice .

while (!q.empty())
{
  int v = q.front(); q.pop();
  for (int to : g[v])
    if (used[to] == 0)
    {
      deg[to]--;

Se dopo la diminuzione il grado del vertice to diventa uguale a , significa che gli è rimasto un unico vicino non rimosso, cioè esso stesso si è trasformato in una foglia. Lo inseriamo nella coda e lo contrassegniamo come rimosso. I vertici del ciclo non raggiungono mai il grado , pertanto il processo si arresta sulla linea circolare.

      if (deg[to] == 1)
      {
        q.push(to);
        used[to] = 1;
      }
    }
}

A questo punto l'uguaglianza used[i] = 0 vale se e solo se il vertice appartiene al ciclo.

Inizializziamo l'array con il valore (tutti i vertici non visitati). La coda a questo punto è già vuota, ma l'assegnazione di una coda vuota la reimposta esplicitamente prima del secondo attraversamento. Successivamente inseriamo ogni vertice del ciclo nella coda e poniamo per esso dist[i] = 0 — tutti i vertici della linea circolare diventano contemporaneamente sorgenti della ricerca in ampiezza.

dist = vector<int>(n + 1, -1);
q = queue<int>();
for (i = 1; i <= n; i++)

  if (used[i] == 0)
  {
    q.push(i);
    dist[i] = 0;
  }

Avviamo la ricerca in ampiezza multi-sorgente. Estraiamo dalla coda il vertice . Ogni suo vicino non visitato to~(dist[to] = –1) riceve la distanza dist[v] + 1 e viene inserito nella coda. I vertici del ciclo sono contrassegnati con distanza fin dall'inizio, pertanto l'attraversamento non ritorna sull'anello e si propaga solo in profondità negli alberi pendenti. Poiché i vertici vengono estratti dalla coda in ordine non decrescente di distanza, ogni valore dist[to] è il più breve possibile.

while (!q.empty())
{
  int v = q.front(); q.pop();
  for (int to : g[v])
    if (dist[to] == -1)
    {
      q.push(to);
      dist[to] = dist[v] + 1;
    }
  }

Stampiamo le distanze dalle stazioni fino alla linea circolare in ordine dei loro numeri.

for (i = 1; i <= n; i++)
   printf("%d ", dist[i]);
printf("\n");