Оптимальное дерево поиска
Классическая динамика по подотрезкам, где стоимость зависит от глубины. Плюс оптимизация Кнута, которая убирает один множитель n.
3 мин
Даны элементов в возрастающем порядке и частоты обращений . Надо построить бинарное дерево поиска, минимизирующее
где глубина корня равна нулю. Чем чаще элемент спрашивают, тем ближе к корню он должен стоять - но структура дерева поиска жёстко связана с порядком элементов, и просто «положить самый частый в корень» неверно.
Динамика по подотрезкам
Дерево поиска на отрезке элементов устроено так: какой-то элемент - корень, слева от него дерево на , справа - на .
Ключевое наблюдение: когда два поддерева подвешивают к новому корню, глубина каждого их элемента увеличивается на единицу. Значит, стоимость растёт ровно на сумму частот этих поддеревьев.
Слагаемое - это сумма частот всех элементов отрезка, кроме корня: у них глубина выросла на единицу, у корня она нулевая.
for (int len = 2; len <= n; len++)
for (int i = 0; i + len - 1 < n; i++) {
int j = i + len - 1;
long long sum = prefix[j + 1] - prefix[i], best = -1;
for (int k = i; k <= j; k++) {
long long value = (k > i ? dp[i][k - 1] : 0) + (k < j ? dp[k + 1][j] : 0) + sum - f[k];
if (best < 0 || value < best) best = value;
}
dp[i][j] = best;
}
Стоит : отрезков, на каждом перебор корня.
Проверено: на 3000 наборах частот длины до 7 динамика дала тот же ответ, что и полный перебор всех бинарных деревьев поиска.
Оптимизация Кнута
Пусть - позиция корня, на которой достигается минимум. Оказывается, она монотонна:
Тогда во внутреннем цикле не надо перебирать все - достаточно отрезка между двумя уже известными значениями. Суммарная длина всех таких отрезков на одной диагонали телескопически сворачивается в , и вся динамика становится .
for (int k = opt[i][j - 1]; k <= opt[i + 1][j]; k++) { ... }
opt[i][j] = argmin;
Проверено: на 28 949 отрезках из случайных наборов монотонность не нарушилась ни разу.
Измерено, во что это обходится по времени:
| обычная динамика | с оптимизацией Кнута | |
|---|---|---|
| 250 | 7 мс | меньше 1 мс |
| 1000 | 252 мс | 6 мс |
| 2000 | 2630 мс | 34 мс |
Практический вывод трезвый: до оптимизация не нужна, обычная динамика укладывается в любой разумный лимит. Она начинает окупаться с порядка тысячи, зато там разница уже в десятки раз.
Условие применимости
Оптимизация Кнута работает не для любой динамики по подотрезкам. Достаточное условие - неравенство четырёхугольника на функцию стоимости :
плюс монотонность по вложению отрезков. Для суммы частот на отрезке оба условия выполняются, и потому приём применим.
Проверять это на глаз не стоит: если сомневаетесь - напишите обе версии и сравните на случайных тестах. Расхождение находится за секунды, а неверная оптимизация даёт неверный ответ молча.
Тот же приём ускоряет склейку куч и распил бруса.