Перейти до основного вмісту

Сильна орієнтація

Сильна орієнтація неорієнтованого графа — це призначення напрямку кожному ребру так, щоб граф став сильно зв'язним. Тобто після орієнтації ми маємо бути спроможні потрапити з будь-якої вершини в будь-яку іншу, рухаючись уздовж орієнтованих ребер.

Коли підходить цей алгоритм?
  • Треба спрямувати ребра неорієнтованого графа так, щоб граф став сильно зв'язним?
  • Граф зв'язний і без мостів (за теоремою Роббінса лише такі графи мають сильну орієнтацію)? (спершу знайди мости)
  • Потрібна орієнтація з мінімальною кількістю компонент сильної зв'язності? (тоді знадобиться розбиття на SCC)

Розв'язок

Зрозуміло, що це можливо не для кожного графа. Розглянемо міст у графі. Ми мусимо призначити йому напрямок, і цим робимо цей міст «прохідним» лише в одному напрямку. Це означає, що ми не можемо потрапити з одного кінця моста в інший, а отже, не можемо зробити граф сильно зв'язним.

Тепер розглянемо DFS у зв'язному графі без мостів. Очевидно, що ми відвідаємо кожну вершину. А оскільки мостів немає, ми можемо видалити будь-яке ребро дерева DFS і все одно зможемо потрапити з-під цього ребра в точку над ним, скориставшись шляхом, що містить принаймні одне зворотне ребро. Звідси випливає, що з будь-якої вершини ми можемо дістатися кореня дерева DFS. Так само з кореня дерева DFS ми можемо відвідати будь-яку обрану вершину. Ми знайшли сильну орієнтацію!

Іншими словами, щоб сильно орієнтувати зв'язний граф без мостів, запускаємо на ньому DFS і спрямовуємо ребра дерева DFS від кореня DFS, а всі інші ребра — від нащадка до предка в дереві DFS.

Твердження про те, що зв'язні графи без мостів — це і є саме ті графи, які мають сильну орієнтацію, називається теоремою Роббінса.

Розширення задачі

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

Зрозуміло, що кожну компоненту графа можна розглядати окремо. Тепер, оскільки сильно орієнтувати можна лише графи без мостів, тимчасово видалимо всі мости. У результаті ми отримаємо певну кількість компонент без мостів (а саме скільки було компонент на початку + скільки було мостів), і ми знаємо, що кожну з них можна сильно орієнтувати.

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

Реалізація

Тут на вхід подається n — кількість вершин, m — кількість ребер, а далі m рядків з описом ребер.

На виході в першому рядку — мінімальна кількість SCC, а в другому рядку — рядок з m символів, де > — означає, що відповідне ребро з входу орієнтоване від лівої вершини до правої (як у вхідних даних), а < — навпаки.

Це алгоритм пошуку мостів, модифікований так, щоб також орієнтувати ребра; ви так само можете спочатку орієнтувати ребра, а другим кроком підрахувати SCC на орієнтованому графі.

vector<vector<pair<int, int>>> adj; // список суміжності - пари вершина і ребро
vector<pair<int, int>> edges;

vector<int> tin, low;
int bridge_cnt;
string orient;
vector<bool> edge_used;
void find_bridges(int v) {
static int time = 0;
low[v] = tin[v] = time++;
for (auto p : adj[v]) {
if (edge_used[p.second]) continue;
edge_used[p.second] = true;
orient[p.second] = v == edges[p.second].first ? '>' : '<';
int nv = p.first;
if (tin[nv] == -1) { // якщо nv ще не відвідана
find_bridges(nv);
low[v] = min(low[v], low[nv]);
if (low[nv] > tin[v]) {
// міст між v і nv
bridge_cnt++;
}
} else {
low[v] = min(low[v], tin[nv]);
}
}
}

int main() {
int n, m;
scanf("%d %d", &n, &m);
adj.resize(n);
tin.resize(n, -1);
low.resize(n, -1);
orient.resize(m);
edges.resize(m);
edge_used.resize(m);
for (int i = 0; i < m; i++) {
int a, b;
scanf("%d %d", &a, &b);
a--; b--;
adj[a].push_back({b, i});
adj[b].push_back({a, i});
edges[i] = {a, b};
}
int comp_cnt = 0;
for (int v = 0; v < n; v++) {
if (tin[v] == -1) {
comp_cnt++;
find_bridges(v);
}
}
printf("%d\n%s\n", comp_cnt + bridge_cnt, orient.c_str());
}

Задачі для практики