EduBrick

Эйлеров обход и LCA

Выписать вершины в порядке обхода, включая возвраты, — и LCA превращается в минимум на отрезке массива.

3 мин

Есть способ искать LCA, не поднимаясь по дереву вовсе. Он сводит задачу к минимуму на отрезке — а это уже решённая задача.

Эйлеров обход

Заменим мысленно каждое ребро дерева на два ориентированных, в обе стороны. У каждой вершины степени входа и выхода станут равны, значит, в таком графе есть эйлеров цикл — маршрут по всем рёбрам ровно по разу.

Строить его отдельно не нужно: обычный обход в глубину его и обходит. Достаточно выписывать вершину при входе и после возврата из каждого сына.

void dfs(int v, int p, int d) {
    pos[v] = order.size();
    order.push_back(v);
    depth.push_back(d);
    for (int u : g[v]) if (u != p) {
        dfs(u, v, d + 1);
        order.push_back(v);            // вернулись — выписываем снова
        depth.push_back(d);
    }
}

Длина массива — 2n12n - 1: вершина выписывается один раз при входе и по разу на каждое ребро вверх.

Рядом храним глубины. pos[v] — какое-нибудь вхождение vv; какое именно, неважно, поэтому запоминаем первое.

Сведение

Возьмём произвольные вхождения uu и vv в массив обхода. Вершина с наименьшей глубиной на отрезке между ними — это LCA.

Доказательство в двух шагах.

LCA на отрезке есть. Кусок маршрута из uu в vv обязан пройти по всем вершинам пути между ними в дереве, а путь в дереве единственный и проходит через LCA.

Ничего выше LCA на отрезке нет. Чтобы попасть выше, обход должен был бы выйти из LCA. Но выйдя из вершины, обход в глубину в неё больше не возвращается — и до vv мы бы уже не добрались.

Значит, на отрезке лежит путь между uu и vv, возможно, ещё какие-то поддеревья, свисающие с него вниз, и ничего выше. Минимум глубины достигается на LCA.

Реализация

int lca(int u, int v) {
    int l = pos[u], r = pos[v];
    if (l > r) swap(l, r);                    // про это забывают чаще всего
    return order[argmin_depth(l, r)];         // минимум на отрезке
}

Перестановка границ обязательна: запрос могли дать в любом порядке, а вхождение vv может оказаться левее вхождения uu.

Проверено: на 20 000 деревьев, около 1,4 миллиона запросов, совпало с подъёмом по родителям и с обоими вариантами двоичных подъёмов.

Чем искать минимум

Дальше это уже не задача про деревья.

структура предподсчёт память запрос
sparse table O(nlogn)O(n \log n) O(nlogn)O(n \log n) O(1)O(1)
дерево отрезков O(n)O(n) O(n)O(n) O(logn)O(\log n)

Массив не меняется, поэтому дерево отрезков здесь избыточно — оно умеет обновления, которые не нужны. Берут его тогда, когда важна линейная память.

Sparse table даёт запрос за константу. При 10710^7 запросов разница с логарифмом — это разница между «зашло» и «не зашло».

Что ещё даёт эйлеров обход

Расстояние между вершинамиdepu+depv2deplca\text{dep}_u + \text{dep}_v - 2\,\text{dep}_{\text{lca}}, без отдельных структур.

Поддерево как отрезок. Есть родственный вариант обхода, где вершина выписывается только при входе; тогда поддерево занимает непрерывный кусок — про это в статье про времена входа и выхода. Не путайте эти два массива: у них разная длина и разное назначение.