Алгебра пометок
Присвоение стирает всё под собой; когда операций две, пометка становится парой с фиксированным порядком.
2 мин
Прибавления складываются: две пометки и дают , порядок неважен. С присвоением так не выйдет.
Присвоение
Присвоение стирает всё, что было под ним, — и накопленные прибавления, и предыдущие присвоения.
void apply(int v, int tl, int tr, long long x) {
sum[v] = x * (tr - tl + 1);
assigned[v] = true;
value[v] = x;
}
Флаг assigned обязателен отдельно от значения: присвоить можно и ноль, и по value[v] == 0 отличить «присвоено ноль» от «пометки нет» невозможно.
Схема «добавлять по дороге» здесь не работает: пометка предка обесценивает всё, что лежит ниже. Нужен честный push в начале и изменения, и запроса.
Когда операций две
Если в задаче есть и присвоение, и прибавление, пометка становится парой с фиксированным порядком применения:
Присвоение может отсутствовать — тогда преобразование это просто «прибавить ».
Композиция:
- приходит прибавление : было «присвоить , прибавить », стало «присвоить , прибавить »; присвоение не трогаем;
- приходит присвоение : оно стирает всё, новая пометка — .
Обе строчки короткие, и обе легко написать неправильно. Проверка: примените к узлу присвоение, потом прибавление, потом снова присвоение — должно остаться только последнее присвоение.
void applyAssign(int v, int len, long long x) {
sum[v] = x * len;
hasAssign[v] = true; assignVal[v] = x; addVal[v] = 0;
}
void applyAdd(int v, int len, long long d) {
sum[v] += d * len;
addVal[v] += d;
}
В push порядок обязателен: сначала протолкнуть присвоение, потом прибавление. Наоборот — и прибавление, накопленное после присвоения, будет стёрто.
Как думать про пометки вообще
Пометка — это функция, применяемая ко всему отрезку. Композиция пометок — композиция функций, и вопрос «складываются ли пометки» — это вопрос «замкнут ли ваш класс функций относительно композиции».
Прибавления образуют группу сдвигов: . Присвоения с прибавлениями — класс аффинных функций вида и , и он тоже замкнут.
А вот «прибавить, но не больше чем до » — уже нет: композиция двух таких операций не выражается третьей того же вида. Такие задачи решаются иначе — например, деревом с амортизацией (Ji Driver segment tree).
Практический вывод: прежде чем писать пометку, проверьте, что две ваши пометки складываются в одну того же вида. Если не складываются, дерево в лоб не поможет.