Бакинське метро#11865

Бакинське метро (Розбір)

Схема метро являє собою зв'язний неорієнтований граф з вершин та рёбер. Зв'язний граф на вершинах, що містить рівно n - 1 ребро, є деревом. Додавання ще одного ребра до дерева створює рівно один цикл. Тому граф з умови складається з єдиного циклу (кільцевої лінії), до вершин якого підвішені дерева (можливо, порожні).

Для кожної вершини потрібно знайти довжину найкоротшого шляху до найближчої вершини циклу. Для вершин циклу відповідь дорівнює . Задача розв'язується у два етапи.

Етап 1. Виділення циклу: послідовне видалення листків

Обчислимо ступені всіх вершин. Помістимо в чергу всі вершини ступеня , тобто листки. Поки черга не порожня, видобуваємо з неї вершину і позначаємо її як видалену. Потім зменшуємо на ступінь кожного її сусіда, який ще не видалений. Якщо ступінь сусіда став рівним , то він сам перетворився на листок, і ми додаємо його в чергу.

Після завершення процесу невидаленими залишаться тільки вершини циклу. Це випливає з двох спостережень:

Етап 2. Багатоджерельний пошук у ширину

Покладемо d[v] = 0 для всіх вершин циклу та d[v] = –1 (не відвідана) для решти. Помістимо всі вершини циклу в чергу одночасно та запустимо з них пошук у ширину. Коли вершина видобувається з черги, кожен її невідвіданий сусід отримує значення d[w] = d[u] + 1 і додається в чергу.

Пошук у ширину з множини джерел обробляє вершини у порядку незменшення відстані до цієї множини. Тому дорівнює мінімальній кількості переходів від до кільцевої лінії. Оскільки все, що лежить поза циклом, є деревом, шлях від кожної станції до кільця насправді єдиний.

Цей процес нагадує зворотне топологічне сортування: ми послідовно видаляємо листки, поки не залишаться тільки "внутрішні" вершини — саме вони і становлять цикл.

Далі запускаємо пошук у ширину одночасно з усіх вершин, що входять до циклу. Таким чином для кожної вершини, що залишилася, визначаємо мінімальну кількість кроків (переходів) до найближчої вершини кільця.

Приклад

У першому прикладі всі вершини входять до одного циклу, тому відстань для кожної з них дорівнює .

У другому прикладі цикл утворюють вершини та , а решта вершин знаходяться на деякій відстані від нього.

Реалізація алгоритму

Оголосимо список суміжності графа , а також допоміжні масиви:

Черга використовується на обох етапах алгоритму: спочатку для послідовного видалення листків, потім для багатоджерельного пошуку в ширину.

vector\u003cvector\u003cint\u003e \u003e g;
vector\u003cint\u003e used, dist, deg;
queue\u003cint\u003e q;

Читаємо вхідні дані. Будуємо граф.

scanf("%d", \u0026n);
g.resize(n + 1);
for (i = 0; i \u003c n; i++)
{
  scanf("%d %d", \u0026a, \u0026b);
  g[a].push_back(b);
  g[b].push_back(a);
}

Обчислюємо початкові ступені вершин: ступінь вершини дорівнює розміру її списку суміжності . Усі листки, тобто вершини ступеня , заносимо в чергу і одразу позначаємо як видалені (used[i] = 1).

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

Послідовно видаляємо листки. Зовні цей цикл схожий на пошук у ширину, однак вершина потрапляє в чергу не при першому виявленні, а тільки тоді, коли сама стає листком. Видобуваємо з черги вершину і перебираємо її сусідів . Сусідів, уже позначених як видалені, пропускаємо. Для кожного невидаленого сусіда зменшуємо ступінь на , оскільки ребро (v, to) зникає з графа разом з вершиною .

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

Якщо після зменшення ступінь вершини to став рівним , то у неї залишився єдиний невидалений сусід, тобто вона сама перетворилася на листок. Заносимо її в чергу і позначаємо як видалену. Вершини циклу ніколи не досягають ступеня , тому процес зупиняється на кільцевій лінії.

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

На даний момент рівність used[i] = 0 виконується тоді і тільки тоді, коли вершина належить циклу.

Ініціалізуємо масив значенням (усі вершини не відвідані). Черга на цей момент уже порожня, але присвоєння порожньої черги явно скидає її перед другим обходом. Потім кожну вершину циклу заносимо в чергу і покладаємо для неї dist[i] = 0 — усі вершини кільцевої лінії одночасно стають джерелами пошуку в ширину.

dist = vector\u003cint\u003e(n + 1, -1);
q = queue\u003cint\u003e();
for (i = 1; i \u003c= n; i++)

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

Запускаємо багатоджерельний пошук у ширину. Видобуваємо з черги вершину . Кожен її невідвіданий сусід to~(dist[to] = –1) отримує відстань dist[v] + 1 і заноситься в чергу. Вершини циклу позначені відстанню з самого початку, тому обхід не повертається на кільце і поширюється тільки вглиб висячих дерев. Оскільки вершини видобуваються з черги у порядку незменшення відстані, кожне значення dist[to] є найкоротшим.

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;
    }
  }

Виводимо відстані від станцій до кільцевої лінії у порядку їхніх номерів.

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