Дерево кратчайших путей
Из $m$ рёбер для кратчайших путей важны $n-1$. Плюс запуск обхода сразу из множества вершин.
4 мин
Массив предков, который заводят для восстановления пути, — это не служебная структура, а полноценный объект. Он задаёт дерево.
Определение
Запустим Дейкстру из вершины и запомним, откуда пришло финальное значение:
if (d[v] + c < d[to]) {
d[to] = d[v] + c;
par[to] = v;
q.push({d[to], to});
}
У каждой достижимой вершины ровно один предок, у — ни одного. Получается дерево с корнем в — дерево кратчайших путей. Путь по дереву от корня до вершины и есть кратчайший путь до неё.
Тот же объект для невзвешенного графа даёт обход в ширину; там он называется деревом обхода и разбирается отдельно.
Главное свойство
Выбросим из графа все рёбра, кроме ребра дерева. Ни одно кратчайшее расстояние от не изменится.
Доказательство очевидно из построения: расстояние до каждой вершины достигается на пути по дереву, и он остался целым.
Менее очевидное следствие: если ребро не входит в дерево, то увеличение его веса не может изменить ни одно кратчайшее расстояние. Кратчайшие пути его не используют.
Зачем это нужно
Задача: 300 вершин, до рёбер с весами. Разрешено удвоить вес одного любого ребра. Сделать кратчайшее расстояние из 1 в как можно больше.
В лоб: перебрать ребро, удвоить, запустить Дейкстру. Это — при и порядка операций, безнадёжно.
С деревом: кандидатов не , а . Все остальные рёбра на ответ не влияют. Получается — при это , спокойно.
long long best = base; // расстояние без изменений
for (int v = 0; v < n; v++) {
if (par_edge[v] < 0) continue;
best = max(best, dijkstra_with_doubled(par_edge[v])[n - 1]);
}
Обратите внимание: Дейкстру внутри надо брать за , а не за . При версия с кучей даёт лишний логарифм там, где он не нужен: мало, велико.
Проверено: на 15 935 случайных графах перебор только по рёбрам дерева дал тот же максимум, что перебор по всем рёбрам.
Важная оговорка: дерево не единственно. Если до вершины есть несколько кратчайших путей, в дерево попадёт один из них — какой именно, зависит от порядка обхода. Свойство от этого не страдает: удвоение ребра, не попавшего в это дерево, ответ не изменит, потому что альтернативный путь той же длины остаётся.
Обход из множества источников
Второй приём той же природы. Нужно расстояние от ближайшей вершины из заданного множества — не от конкретной, а от любой.
Запускать обход из каждой вершины по очереди — запусков. Не нужно: кладём в очередь сразу все вершины с расстоянием ноль.
vector<long long> d(n, INF);
priority_queue<pair<long long, int>,
vector<pair<long long, int>>,
greater<>> q;
for (int s : sources) { d[s] = 0; q.push({0, s}); }
// дальше всё как обычно
Корректность видна через фиктивную вершину: добавим вершину и рёбра веса 0 из неё во все источники. Расстояние от до и есть минимум по источникам, а начальное заполнение очереди — ровно первый шаг такой Дейкстры.
Проверено: на трёх тысячах случайных графов многоисточниковый запуск совпал с запуском из фиктивной вершины.
Стоит это один обход, а не . Типичные постановки: «расстояние до ближайшего пожарного гидранта», «до ближайшей заправки», «до ближайшей клетки с водой».
Что не переносится
Дерево кратчайших путей — не минимальное остовное дерево. Это разные объекты, и суммарный вес рёбер у них разный.
Простой пример: треугольник с рёбрами , , . Дерево кратчайших путей от — это два ребра по 2, суммарный вес 4. Минимальное остовное — рёбра 1 и 2, суммарный вес 3. Оно короче, но расстояние от до по нему равно 3, а не 2.