Сервер#10456

Сервер (Розбір)

Граф містить до 10^4 вершин і до 4 \cdot 10^4 ребер, тому зберігатимемо його у вигляді списку суміжності. Усі ваги ребер додатні, отже, найкоротші відстані можна знаходити алгоритмом Дейкстри.

Постановка задачі в термінах графа. Позначимо через довжину найкоротшого шляху між вершинами і . Якщо сервер стоїть у місті , то гість із міста c_i отримує затримку d(c_i, v). Сумарна затримка всіх гостей дорівнює

S(v) = d(c_1, v) + d(c_2, v) + ... + d(c_k, v)

Потрібно знайти вершину з мінімальним значенням , а при рівності — з найменшим номером.

Чому запускаємо Дейкстру з міст гостей. Можна було б для кожної вершини-кандидата запустити Дейкстру з і скласти відстані до міст гостей. Але кандидатів до 10^4, і стільки запусків занадто багато. Зауважимо, що граф неорієнтований, тому d(c_i, v) = d(v, c_i). Отже, один запуск Дейкстри з міста гостя c_i одразу дає затримку цього гостя для всіх можливих місць сервера. Гостей не більше , достатньо запусків.

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

Далі для кожного гостя по черзі:

  1. Запускаємо алгоритм Дейкстри з вершини c_i і отримуємо масив dist[1..n], де dist[v] = d(c_i, v). Це затримка -го гостя, якщо сервер знаходиться в місті .

  2. Проходимо по всіх вершинах v = 1, 2, ..., n і виконуємо sum[v] += dist[v]. Так ми додаємо внесок -го гостя до сумарної затримки кожного можливого місця розміщення сервера.

Після обробки всіх гостей кожен елемент містить рівно доданків — затримки всіх гостей до вершини , тобто sum[v] = S(v).

Вибір відповіді. Перебираємо вершини v = 1, 2, ..., n у порядку зростання номерів і запам'ятовуємо вершину з мінімальною сумою. Відповідь оновлюємо лише при строгій нерівності sum[v] < best. Тоді при кількох вершинах з однаковою мінімальною сумою залишиться перша з них, тобто з найменшим номером.

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

Приклад

Граф, наведений у прикладі, має такий вигляд:

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

Далі для кожної вершини графа знайдемо суму відстаней до всіх міст із гостями. Мінімальна сума відстаней дорівнює , і вона досягається у вершині з найменшим номером .

Таким чином, сервер слід розмістити в місті . Сумарна відстань від нього до міст, де проживають гості, становить 5 + 0 + 8 = 13.

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

Оголосимо константу нескінченність.

#define INF 0x3F3F3F3F

Структура зберігатиме інформацію про ребро: вершину , у яку веде ребро, і вагу цього ребра.

struct edge
{
  int node, dist;
  edge(int node, int dist) : node(node), dist(dist) {}
};

Оголосимо компаратор для сортування пар (node, dist) за спаданням значення .

bool operator< (edge a, edge b)
{
  return a.dist > b.dist;
}

Оголосимо список суміжності для представлення графа.

vector<vector<edge> > g;

Функція Dijkstra реалізує алгоритм Дейкстри. На вхід вона приймає граф і початкову вершину . Значенням, що повертається, є масив , де містить найкоротшу відстань від вершини до вершини .

void Dijkstra(vector<vector<edge> >& g, vector<int>& d, int start)
{
  priority_queue<edge> pq;
  pq.push(edge(start, 0));

  d = vector<int>(n + 1, INF);
  d[start] = 0;

  while (!pq.empty())
  {
    edge e = pq.top(); pq.pop();
    int v = e.node;

    if (e.dist > d[v]) continue;

    for (auto e : g[v])
    {
      int to = e.node;
      int cost = e.dist;
      if (d[v] + cost < d[to])
      {
        d[to] = d[v] + cost;
        pq.push(edge(to, d[to]));
      }
    }
  }
}

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

scanf("%d %d %d", &n, &m, &k);
c.resize(k);
for (i = 0; i < k; i++)
  scanf("%d", &c[i]);

Читаємо вхідний зважений граф.

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

Запускаємо алгоритм Дейкстри з міст, у яких проживають гості.

dp.resize(n + 1);
for (i = 0; i < k; i++)
{
  Dijkstra(g, dist, c[i]);

  for (j = 1; j <= n; j++)
   dp[j] += dist[j];
}

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

res = INF;
for (i = 1; i <= n; i++)
{
  if (dp[i] < res)
  {
    res = dp[i];
    ptr = i;
  }
}

Виводимо відповідь.

printf("%d %d\n", ptr, dp[ptr]);