Похід в кіно#4463

Похід в кіно (Розбір)

Оскільки між будь-якою парою міст існує рівно один шлях, дорожня мережа країни утворює дерево. Щоб мінімізувати загальні витрати, Чіпу та Дейлу вигідно зустрітися якомога раніше. Це станеться, якщо вони зустрінуться у своєму найближчому спільному предку, після чого зможуть разом поїхати на таксі до столиці, де знаходиться кінотеатр.

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

Для знаходження найближчого спільного предка (LCA) використовується метод двійкового підйому. Граф зберігається у вигляді списку суміжності в масиві . Також оголошуються необхідні глобальні масиви для роботи алгоритму LCA:

#define MAX 100010
#define LOGMAX 18
vector<int> g[MAX];
int timer, d[MAX], f[MAX], dist[MAX];
int up[MAX][LOGMAX];

Функція dfs виконує обхід дерева в глибину, починаючи з вершини . Вершина є батьком вершини . Відстань від столиці (вершини з номером ) до вершини дорівнює ; це значення зберігається в масиві dist[v]. У процесі обходу також обчислюються мітки часу входу та виходу з вершини — та .

void dfs (int v, int p = 1, int len = 0) 
{
  d[v] = timer++;

Зберігаємо безпосереднього батька вершини .

  up[v][0] = p; 

Зберігаємо відстань від кореня (столиці) до вершини .

  dist[v] = len;

Будуємо таблицю двійкових предків.

  for (int i = 1; i <= l; i++)
    up[v][i] = up[up[v][i-1]][i-1];

Із вершини розглядаються всі можливі переходи до сусідніх вершин. Для кожної вершини , з'єднаної з ребром дерева (v, to), виконується перехід.

  for (int to : g[v])
    if (to != p) dfs (to, v, len + 1);
  f[v] = timer++;
}

Функція Parent повертає істину, якщо вершина є предком вершини .

int Parent(int a, int b)
{
  return (d[a] <= d[b]) && (f[a] >= f[b]);
}

Функція LCA обчислює найближчого спільного предка (LCA) двох вершин та у дереві за допомогою методу двійкового підйому та таблиці предків .

int LCA (int a, int b) 
{
  if (Parent(a, b)) return a;
  if (Parent(b, a)) return b;
  for (int i = l; i >= 0; i--)
    if (!Parent(up[a][i], b)) a = up[a][i];
  return up[a][0];
}

Основна частина програми. Читаємо вхідні дані.

scanf("%d %d",&n,&Price);

Обчислюємо значення l = ⌈log_2n⌉.

l = 1;
while ((1 << l) <= n)  l++;

Будуємо неорієнтований граф, який за умовою задачі є деревом.

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

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

dfs(1);

Послідовно обробляємо запитів.

scanf("%d",&q);
for(i = 0; i < q; i++)
{
  scanf("%d %d",&x,&y);
  lca = LCA(x, y);

До зустрічі Чіп проїде dist[x] - dist[lca] км, а Дейл dist[y] - dist[lca] км. Разом на таксі вони проїдуть рівно dist[lca] км.

  Chip = dist[x] - dist[lca];
  Deil = dist[y] - dist[lca];

Обчислюємо та виводимо підсумкові витрати.

  printf("%.5lf %.5lf\n", (Chip + dist[lca] / 2.0) * Price, 
          (Deil + dist[lca] / 2.0) * Price);
}