Level Ancestor и лестницы
Подняться ровно на k уровней. Один двоичный прыжок плюс обращение в массив — и запрос стоит константу.
3 мин
Задача Level Ancestor: дана вершина и число , найти предка , стоящего ровно на рёбер выше.
Двоичные подъёмы решают её сразу: раскладываем по битам и делаем прыжки. на запрос. Но можно за .
Разбиение на длиннейшие пути
Определим глубину вершины как расстояние до самого далёкого листа в её поддереве.
Разобьём дерево на вертикальные пути: начиная из корня, продолжаем путь в того сына, у которого глубина наибольшая. Остальные сыновья начинают свои пути.
void build(int v, int p, int id) {
pathId[v] = id;
pathPos[v] = paths[id].size();
paths[id].push_back(v);
int best = -1;
for (int u : g[v]) if (u != p)
if (best == -1 || deepest[u] > deepest[best]) best = u;
if (best != -1) build(best, v, id);
for (int u : g[v]) if (u != p && u != best) {
paths.push_back({});
build(u, v, paths.size() - 1);
}
}
Если бы ответ всегда лежал на том же пути, что и , запрос сводился бы к paths[pathId[v]][pathPos[v] - k] — обращение в массив, .
Но путь может кончиться раньше, чем мы поднимемся на . Тогда так не выйдет.
Лестницы
Приём: удлиним каждый путь вверх вдвое. Если в пути было вершин, допишем в начало ещё вершин-предков (или меньше, если упёрлись в корень).
Эти дописанные вершины принадлежат другим путям — они дублируются. Памяти уходит вдвое больше, зато появляется запас сверху. Такое удлинённое разбиение называется лестничной декомпозицией.
Запрос за константу
Пусть — наибольшая степень двойки, не превосходящая . Сделаем один двоичный прыжок на и попадём в вершину . Осталось подняться на .
Утверждение: этот остаток гарантированно лежит внутри лестницы вершины .
Почему. Из вниз идёт путь длины хотя бы — по нему мы только что пришли из . Значит, глубина не меньше , и длиннейший путь через имеет длину хотя бы . Удлинив его вдвое, мы получили запас сверху тоже не меньше . А подняться осталось меньше, чем на .
int levelAncestor(int v, int k) {
if (k == 0) return v;
int l = __lg(k);
int w = up[l][v]; // единственный двоичный прыжок
int rest = k - (1 << l);
return ladders[pathId[w]][ladderPos[w] - rest];
}
Предподсчёт — из-за таблицы двоичных подъёмов. Запрос .
Проверено: 20 000 деревьев, 765 534 запроса — ответы совпали с подъёмом по родителям, и лестницы хватало во всех случаях. Это как раз тот шаг рассуждения, который проще проверить, чем перепроверить на бумаге.
Родственная декомпозиция
Если продолжать путь не в самого «глубокого» сына, а в сына с наибольшим размером поддерева, получится тяжёло-лёгкая декомпозиция (HLD). У неё другое свойство: путь между любыми двумя вершинами пересекает не более путей разбиения. Она нужна для запросов на путях с изменениями, и это отдельная тема.
Разница в одном слове — «глубокий» против «большого», — а применения разные.