← Все вопросы

Convex Hull Trick: когда ДП-переход можно ускорить и как это понять?

Задан 5 месяцев назад396 просмотров2 ответа
12

Слышал про оптимизацию ДП «выпуклой оболочкой» (Convex Hull Trick). Как понять по виду рекуррентности, что её можно применить? И в чём вообще идея — почему квадрат превращается в почти линейность?

2 ответа

17
✓ Принятый ответ — помог автору

CHT применим, когда переход имеет вид dp[i] = min_{j<i} ( dp[j] + b[j] · x[i] ) (или max), то есть для каждого i ты минимизируешь по j линейную функцию от x[i] с коэффициентом b[j]. Каждое предыдущее состояние j задаёт прямую y = b[j]·x + dp[j]. Найти минимум по всем прямым в точке x[i] — это запрос к нижней огибающей набора прямых. Наивно это O(n) на запрос → O(n²) всего. CHT хранит огибающую и отвечает за O(1) амортизированно (или O(log n)), убирая лишние прямые.

Идея «почему линейно»: оптимальная прямая в нижней огибающей выпукла, и если запросы x[i] монотонны, то указатель оптимума движется только в одну сторону. Добавление прямой и поиск — амортизированно O(1).

// нижняя огибающая для min, x монотонно растут
struct Line { long long k, b; long long eval(long long x){ return k*x + b; } };
vector<Line> hull; int ptr = 0;
bool bad(Line l1, Line l2, Line l3) { // l2 не нужна?
    return (__int128)(l3.b - l1.b) * (l1.k - l2.k)
         <= (__int128)(l2.b - l1.b) * (l1.k - l3.k);
}
void add(long long k, long long b) {           // k убывает
    Line l{k, b};
    while (hull.size() >= 2 && bad(hull[hull.size()-2], hull.back(), l)) hull.pop_back();
    hull.push_back(l);
}
long long query(long long x) {                 // x возрастает
    if (ptr >= (int)hull.size()) ptr = hull.size()-1;
    while (ptr + 1 < (int)hull.size() && hull[ptr+1].eval(x) <= hull[ptr].eval(x)) ptr++;
    return hull[ptr].eval(x);
}

Сложность O(n) при монотонных наклонах и запросах. Грабля: пересечения считай в __int128, иначе переполнение в bad() даст неверную огибающую и WA. Если монотонности нет — бери Li Chao tree или CHT с бинпоиском за O(n log n).

7

Сигнал-маркер «здесь CHT»: ты раскрываешь стоимость перехода и видишь произведение чего-то от j на что-то от i. Например цена (x[i]-x[j])² = x[i]² - 2·x[i]·x[j] + x[j]² — слагаемое -2·x[i]·x[j] это b[j]·x[i], где b[j] = -2·x[j], а x[j]² уходит в dp[j]. После выноса всего, что зависит только от i, за минимум, остаётся линейная по x[i] функция от j → CHT. Если переход неразложим в линейную форму — CHT не поможет, смотри в сторону D&C optimization или Кнута.

Ваш ответ

Войдите, чтобы ответить на вопрос.