EduBrick

Мо с изменениями

Третья координата — время. Шесть операций вместо четырёх, блок n в степени две трети и оценка n в степени пять третьих.

3 мин

Обычный алгоритм Мо требует, чтобы массив не менялся: состояние окна должно меняться маленькими шагами, а изменение элемента ломает это. Ответы, посчитанные до изменения и после, относятся к разным массивам.

Починка неожиданно простая: добавить к запросу третью координату.

Время как координата

Пронумеруем изменения по порядку. Каждому запросу припишем время tt - сколько изменений произошло до него. Теперь состояние описывается тройкой (l,r,t)(l, r, t), и операций становится шесть: сдвинуть левую границу, сдвинуть правую, сдвинуть время - каждое в обе стороны.

Сдвиг времени - это применение или откат одного изменения:

struct Change { int pos, value; };

auto applyTime = [&](int i) {           // и применение, и откат
    Change& u = changes[i];
    if (curL <= u.pos && u.pos <= curR) { del(a[u.pos]); add(u.value); }
    std::swap(a[u.pos], u.value);       // обмен делает операцию обратной себе
};

Обмен вместо присваивания - главная строчка. После него та же самая функция откатывает изменение назад: в changes[i].value теперь лежит старое значение. Переход по времени в обе стороны пишется один раз:

while (time < query.t) applyTime(time++);
while (time > query.t) applyTime(--time);

Проверка «попадает ли изменённая позиция в текущее окно» обязательна: изменение вне окна на счётчики не влияет, но массив поменять всё равно надо - иначе последующий сдвиг границы прочитает не то значение.

Порядок обхода и размер блока

Сортируем по тройке: номер блока ll, номер блока rr, время.

std::sort(queries.begin(), queries.end(), [B](const Ask& x, const Ask& y) {
    int bx = x.l / B, by = y.l / B;
    if (bx != by) return bx < by;
    int rx = x.r / B, ry = y.r / B;
    if (rx != ry) return (bx & 1) ? rx > ry : rx < ry;
    return (rx & 1) ? x.t > y.t : x.t < y.t;
});

Оценка складывается из трёх слагаемых:

указатель сколько ходит
левый O(qB)O(qB)
правый O(n2/B)O(n^2 / B)
время O(qn2/B2)O(q \cdot n^2 / B^2)

Третья строчка - потому что внутри пары блоков (bl,br)(b_l, b_r) время движется монотонно, а таких пар (n/B)2(n/B)^2. Сумма минимальна при

Bn2/3,итого O(n5/3).B \approx n^{2/3}, \qquad \text{итого } O(n^{5/3}).

Сколько это на самом деле

Оценка n5/3n^{5/3} выглядит безобидно, пока не подставишь числа:

nn nnn\sqrt n n5/3n^{5/3}
10410^4 10610^6 4,61054{,}6 \cdot 10^5
10510^5 3,21073{,}2 \cdot 10^7 2,21082{,}2 \cdot 10^8
21052 \cdot 10^5 8,91078{,}9 \cdot 10^7 6,81086{,}8 \cdot 10^8
51055 \cdot 10^5 3,51083{,}5 \cdot 10^8 3,11093{,}1 \cdot 10^9

На 10410^4 версия с изменениями даже дешевле обычной - но там и без Мо всё проходит. А начиная с 21052 \cdot 10^5 разница становится решающей: если в задаче есть изменения и nn больше сотни тысяч, Мо скорее всего не тот инструмент.

Ограничения в условии - подсказка. Задача на Мо с изменениями почти всегда даёт nn и qq до 51045 \cdot 10^4, и это не случайность.

Подробнее про каркас: «Алгоритм Мо».