← Все вопросы

Дерево отрезков на максимальный подотрезок-сумму (maximum subarray) — как сливать узлы?

Задан 10 месяцев назад1.1к просмотров2 ответа
11

Хочу дерево отрезков, отвечающее на запрос «максимальная сумма непрерывного подотрезка внутри [l, r]» (как задача Кадане, но на любом подотрезке и с обновлениями). Что хранить в узле и как корректно объединять детей?

2 ответа

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

В узле храните 4 величины для его подотрезка: sum (вся сумма), pref (максимальный префиксный подотрезок), suf (максимальный суффиксный), best (максимальный подотрезок целиком внутри). Это классический «merge структуры», и именно слияние — суть задачи.

struct Node {
    long long sum, pref, suf, best;
};
Node merge(const Node& a, const Node& b) {
    Node r;
    r.sum  = a.sum + b.sum;
    r.pref = max(a.pref, a.sum + b.pref);
    r.suf  = max(b.suf, b.sum + a.suf);
    r.best = max({a.best, b.best, a.suf + b.pref});
    return r;
}
Node leaf(long long x) {
    // если разрешён пустой подотрезок -> max(x, 0); иначе x
    return {x, x, x, x};
}

Ключ — r.best = max(a.best, b.best, a.suf + b.pref): лучший подотрезок либо целиком слева, либо целиком справа, либо пересекает границу детей (суффикс левого + префикс правого). Префикс/суффикс родителя тоже учитывают, что они могут протянуться через всего ребёнка.

build O(n), точечное обновление O(log n), запрос O(log n). При запросе на [l, r] сливайте Node-ответы канонических узлов в правильном порядке (слева направо!) — операция некоммутативна.

7

Две тонкости, на которых легко получить WA:

  1. Пустой подотрезок разрешён или нет. Если в задаче ответ может быть пустым (сумма 0), то в листе best = max(x, 0) и нейтральный Node — все поля 0/−inf аккуратно. Если подотрезок обязан быть непустым (хотя бы один элемент), то при всех отрицательных числах ответ — максимальный элемент, и нельзя «схлопывать» в 0. Перечитайте условие.

  2. Порядок слияния при запросе. Поскольку merge некоммутативен (a.suf + b.prefb.suf + a.pref), при сборе ответа из канонических узлов отрезка их надо сливать строго слева направо. Если используете рекурсию, которая разбивает на левую и правую части и сливает merge(leftAns, rightAns) — порядок сохраняется автоматически. А вот в итеративном bottom-up дереве надо отдельно копить левый и правый аккумуляторы (как для любой некоммутативной операции).

Ваш ответ

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