Отложенное построение - Алгоритмика
Отложенное построение

Отложенное построение

Рассмотрим нашу любимую задачу суммы на подотрезках, но теперь все индексы лежат не в пределах $10^5$ или $10^6$, а до $10^9$ или даже $10^{18}$.

Все асимптотики нас по прежнему более-менее устраивают:

$$ \log_2 10^6 \approx 20 \\ \log_2 10^9 \approx 30 \\ \log_2 10^{18} \approx 60 $$

Единственная проблема — это этап построения, работающий за линейное от $n$ время и память.

Решить её можно отказавшись от явного создания всех вершин дерева в самом начале. Изначально создадим только лишь корень, а остальные вершины будем создавать на ходу, когда в них потребуется записать что-то не дефолтное — как в lazy propagation:

struct Segtree {
    int lb, rb;
    int s = 0;
    Segtree *l = 0, *r = 0;

    Segtree(int lb, int rb) : lb(lb), rb(rb) {
        // а тут ничего нет
    }

    // создает детей, если нужно
    void extend() {
        if (!l && lb + 1 < rb) {
            int t = (lb + rb) / 2;
            l = new segtree(lb, t);
            r = new segtree(t, rb);
        }
    }
    
    // ...
};

Теперь по аналогии с push будем в начале всех методов будем проверять, что дети-вершины созданы, и создавать их, если это не так.

void add(int k, int x) {
    extend();
    s += x;
    if (l) {
        if (k < l->rb)
            l->add(k, x);
        else
            r->add(k, x);
    }
}

int sum(int lq, int rq) {
    if (lb >= lq && rb <= rq)
        return s;
    if (max(lb, lq) >= min(rb, rq))
        return 0;
    extend();
    return l->sum(lq, rq) + r->sum(lq, rq);
}

Такой метод в основном приеним только к реализации на указателях, однако при использовании индексаций можно хранить вершины не в массиве, а в хеш-таблице: запросы станут работать дольше, но не асимптотически. Отметим также, что в любом случае на каждый запрос потребуется $O(\log n)$ дополнительной памяти для создания новых вершин, что обычно приводит к асимптотике $O(q \log n)$ по памяти.

Также, если все запросы известны заранее, то их координаты можно просто сжать перед обработкой запросов. Автор обычно делает это так:

vector<int> compress(vector<int> a) {
    vector<int> b = a;
    sort(b.begin(), b.end());
    b.erase(unique(b.begin(), b.end()), b.end());
    for (int &x : a) 
        x = int(lower_bound(b.begin(), b.end(), x) - b.begin());
    return a;
}

В большинстве случаев, использовать динамическое построение — это как стрелять из пушки по воробьям. Попробуйте сначала более простые методы: возможно, всё просто в set влезает.