Эта статья — одна из серии. Рекомендуется сначала прочитать все предыдущие.
В качестве финальной оптимизации мы рассмотрим метод, вдохновленный методом множителей Лагранжа, также в олимпиадной среде называемый «лямбда-оптимизацией».
#Модификация задачи
Рассмотрим немного измененную версию исходной задачи. Пусть нам нужно покрыть те же точки, но теперь нас не ограничивают жёстко в количестве отрезков, а просто штрафуют на какую-то константу за использование каждого дополнительного отрезка.
Нашу оптимизируемую функцию тогда можно выразить через следующим образом:
Однако её можно считать по более оптимальной формуле, не сводя к вычислению :Эту динамику можно посчитать за : мы ровно это и делали в прошлой статье про Convex Hull Trick.
#Идея
Наблюдение 1. Если в оптимальном решении для мы для какого-то использовали ровно отрезков, то это же решение будет оптимальным и для .
Наблюдение 2. Если уменьшать , то оптимальное количество отрезков для будет увеличиваться.
Основная идея оптимизации: сделаем бинпоиск по , внутри которого будем находить оптимальное решение для с заданным . Если оптимальное получилось больше , то следующая должна быть меньше, а в противном случае наоборот. Когда совпадёт с , просто выведем чистую стоимость получившегося решения без штрафов.
Таким образом, задача решается за , если сортировку точек для CHT делать заранее, а не внутри бинпоиска.
#Реализация
pair<ll, int> dp[maxn]; // dp[i] - (ответ, число отрезков)
void init() {
for (int i = 0; i < maxn; i++) {
dp[i] = make_pair(inf, 0);
}
}
pair<ll, int> check(ll x) { // это можно соптимизировать
init();
dp[0] = make_pair(0ll, 0); // 1-индексация
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
dp[i] = min(dp[i], {dp[j].first + cost[j + 1][i] + x, dp[j].second + 1});
}
}
return dp[n];
}
ll solve() {
ll l = -1e14; // границы надо подбирать очень аккуратно!
ll r = 1;
while (l + 1 < r) {
ll mid = (l + r) / 2;
pair<ll, int> x = check(mid);
if (x.second >= k)
l = mid;
else
r = mid;
}
pair<ll, int> result = check(l);
return result.first - l * return.second; // вычитаем штрафы
}
Автор извиняется за качество реализации и призывать читателя её переписать и закомитить.
#Существование решения
Мы пропустили один нюанс: почему вообще существует такая , что оптимальное ?
В общем случае возможно, что функция через него «перескакивает», и это действительно проблема: одной лишь монотонности не достаточно, чтобы решать подобным образом произвольные задачи с ограничением на число объектов.
Но в нашей задаче можно заметить следующее: функция нестрого вогнутая (выпуклая вверх) по своему второму аргументу, то есть
Иными словами, дополнительная «выгода» от добавления каждого следующего отрезка не увеличивается.
Теперь, на выражение для модифицированной динамики
можно смотреть как на минимизацию скалярного произведение точек и вектора .
Так как верхняя огибающая точек выпукла, для каждой точки будет существовать , которая упирается в него, и следовательно гарантированно найдется.