Напомню, что как только мы видим дерево (в любой задаче), мы его “подвешиваем”,
то есть выбираем корень. Почти все задачи про деревья удобно решать на
подвешенном дереве, где у каждой вершины кроме корня есть ровно один родитель и,
возможно, несколько детей.
(Это все удобно хранить как вектор списков детей каждой вершины, и вектор
родителей, если надо)
Задача
Дано обычное дерево. Надо найти одно число - сумму расстояний между каждой
парой вершин.
Решение
Конкретно в такой формулировке задачу можно решить почти без динамики по
поддеревьям, если разбить эту сумму расстояний по каждому ребру.
Ответ: Столько раз, сколько путей проходит через это ребро. А именно - K * (N-K), где
K вершин находится по одну сторону от этого ребра, а N-K - по другую (это количество
способов выбрать начало и конец пути).
Усложним задачу
Пусть теперь
а) дерево взвешенное (у ребер есть вес)
Решение не меняется (просто надо умножить на вес ребра).
б) надо найти не сумму ВСЕХ расстояний, а для каждой вершины посчитать сумму
расстояний до неё от каждой другой вершины.
А вот теперь наше решение не прокатывает. Надо придумывать нормальное решение.
Решение
Что хранить как динамику? Очевидно, то, что просят. Давайте хранить в вершине
сумму всех расстояний от этой вершины до ВСЕХ остальных. Но такую динамику даже
непонятно как инициализировать быстро. Тогда давайте для начала насчитаем хотя
бы сумму расстояний до всех вершин в ПОДДЕРЕВЕ.
Такую динамику легко насчитать DFS-ом: в листьях это 0, а переход тоже не очень
сложный: сумма расстояний от вершины до всех потомков - это просто сумма
значений динамики для детей - но надо еще учесть, что в каждом из этих расстояний
путь продлился на одно ребро, вес которого мы знаем (пусть вес ребра, ведущего от
родителя y к y - это w[y]).
Заметим, что мы используем в этой формуле size[x], то есть у нас наша динамика
зависит от еще одной. Мы можем не запускать два раза DFS, а прямо походу DFS
насчитывать обе динамики. В ДП по поддеревьям одни динамики все время
используют другие - это нормально, не бойтесь вводить побольше динамик.
Таким образом мы “снизу вверх” насчитали массив dp, где лежит что-то похожее на
ответ - сумма расстояний от вершины до всех потомков.
Заметим, что после этого в корне уже лежит ответ! То есть для корня задачу мы
решили, потому что потомки корня - это как раз все остальные вершины.
Давайте теперь запустим второй DFS и попытаемся теперь найти настоящий ответ (в
массив dp2, например) для каждой вершины, пересчитывая не через детей, а через
родителя (для корня причем ответ мы уже знаем).
Эту динамику мы считаем уже “сверху вниз” (потому что через родителя, а не через
детей).
Вот и все, мы решили задачу за два DFS. Писать кода нужно довольно мало, а вот
придумывать эти переходы - это и есть самое интересное и сложное (возможно я где-
то ошибся, напишите мне про это).
Идеи
1) Считайте сразу несколько динамик, которые зависят друг от друга
2) На примере этой задаче видно, что иногда выгодно считать ДП снизу вверх,
после чего в корне появляется правильный ответ, и потом сверху вниз.
3) Еще бывает такая идея, которая в этой задаче едва встретилась: насчитывать
динамику “для всего поддерева, кроме вот этого ребенка” или даже “для k
первых детей” (в обоих случаях динамика все еще линейная (!), так как
суммарно сыновей N-1).
В этой задаче нам нужно было расстояние от родителя до ВСЕХ, КРОМЕ
НАШЕГО ПОДДЕРЕВА, и мы вместо того, чтобы заводить еще одну dp, мы
просто вычли из суммы это поддерево, нам повезло, что формула простая.
А вот иногда бывает выгодно записывать это в динамику.