Протипожежна безпека#1937

Протипожежна безпека (Розбір)

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

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

Будувати додаткові станції не потрібно, оскільки з будь-якої вершини графа існує шлях по ребрах в одну з компонент без вихідних ребер.

Приклад

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

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

Вхідний граф зберігаємо у списку суміжності . Оборотний граф (граф, в якому напрямки всіх ребер інвертовані) зберігаємо у списку суміжності .

vector<vector<int> > g, gr;
vector<int> used, top, color, repr;

Функція dfs1 реалізує пошук у глибину на вхідному графі. У масиві зберігаємо порядок вершин, що відповідає моменту завершення їх обробки при обході в глибину.

void dfs1(int v)
{
  used[v] = 1;
  for (int to : g[v])
    if (!used[to]) dfs1(to);
  top.push_back(v);
}

Функція dfs2 реалізує пошук у глибину на оборотному графі. Всі вершини, відвідані в результаті рекурсивних викликів dfs2, належать одній компоненті сильної зв'язності. Всі такі вершини фарбуються кольором . Вершини, що належать одній компоненті сильної зв'язності, мають однаковий колір.

Для кожного кольору в масиві зберігається представник відповідної компоненти — будь-який номер вершини, пофарбованої цим кольором.

void dfs2(int v, int c)
{
  color[v] = c;
  repr[c] = v;
  for (int to : gr[v])
    if (color[to] == -1) dfs2(to, c);
}

Основна частина програми. Читаємо вхідні дані. Будуємо оборотний граф.

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

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

used.resize(n+1);
for (i = 1; i <= n; i++)
  if (!used[i]) dfs1(i);

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

color.assign(n+1, -1);
repr.assign(n+1, -1);
reverse(top.begin(), top.end());

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

c = 0;
for (int v : top)
  if (color[v] == -1) dfs2(v, c++);

Змінна містить кількість компонент сильної зв'язності.

used.assign(c, 1);
for (i = 1; i < g.size(); i++)
  for (int to : g[i])

Перебираємо всі ребра графа і перевіряємо, чи належать вершини та різним компонентам сильної зв'язності. Це визначається за різними кольорами вершин. Якщо вершини знаходяться в різних компонентах, у компоненті з кольором color[i] немає необхідності будувати пожежну станцію, тому встановлюємо used[color[i]] = 0.

  if (color[i] != color[to]) used[color[i]] = 0;
}

Підрахуємо кількість компонент , в яких слід побудувати пожежну станцію.

c = 0;
for (i = 0; i < used.size(); i++)
  if (used[i]) c++;

Виводимо мінімальне необхідне число пожежних станцій.

printf("%d\n",c);

Для кожної компоненти з кольором , з якої не виходить жодного ребра, виводимо її представника (значення repr[i]). Саме ці номери будинків вказують, біля яких слід побудувати пожежні станції.

for (i = 0; i < used.size(); i++)
  if (used[i]) printf("%d ",repr[i]);
printf("\n");