← Все вопросы

Корневая декомпозиция (sqrt decomposition): как отвечать на запросы суммы на отрезке за O(sqrt n)?

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

Хочу разобраться с sqrt-декомпозицией на простом примере: запросы «сумма на отрезке [l, r]» и «изменить a[i]». Знаю, что есть дерево отрезков, но хочу понять корневую: почему именно блоки размера sqrt(n) и откуда O(sqrt n)?

2 ответа

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

Идея: разбиваем массив на блоки длины примерно B = sqrt(n). Для каждого блока заранее храним его сумму. Тогда:

  • Точечное обновление a[i] += d: меняем a[i] и сумму его блока — O(1).
  • Запрос суммы [l, r]: края обрабатываем поэлементно (не больше двух неполных блоков, каждый длины < B → O(B)), а целые блоки между ними берём уже посчитанными суммами (их не больше n/B штук → O(n/B)). Итого O(B + n/B), минимум при B = sqrt(n) → O(sqrt n) на запрос.
int n, B;
vector<long long> a, blockSum;

void build() {
    B = max(1, (int)sqrt((double)n));
    blockSum.assign((n + B - 1) / B, 0);
    for (int i = 0; i < n; i++) blockSum[i / B] += a[i];
}
void update(int i, long long val) {
    blockSum[i / B] += val - a[i];
    a[i] = val;
}
long long query(int l, int r) {            // включительно
    long long res = 0;
    int bl = l / B, br = r / B;
    if (bl == br) {                         // один блок целиком в крае
        for (int i = l; i <= r; i++) res += a[i];
    } else {
        for (int i = l; i < (bl + 1) * B; i++) res += a[i];     // левый край
        for (int b = bl + 1; b < br; b++) res += blockSum[b];   // целые блоки
        for (int i = br * B; i <= r; i++) res += a[i];          // правый край
    }
    return res;
}

Построение O(n), запрос/обновление O(sqrt n), память O(n). Выбор B = sqrt(n) — это минимум суммы B + n/B по AM-GM. Корневая проще дерева отрезков в реализации и легко обобщается на «отложенные» обновления на блок (пометки на блок + ленивое пересчитывание края).

6

Когда корневая лучше дерева отрезков: когда операция не ассоциативна красиво или сложно влезает в lazy-дерево, но легко агрегируется по блоку (например, «сколько чисел в [l,r] больше x» — храни отсортированный блок и делай бинпоиск внутри блока, O(sqrt n log n)). Минусы корневой: константа O(sqrt n) на запрос против O(log n) у дерева — при n = 1e5 это ~316 против ~17, разница в ~18 раз. Поэтому при большом числе запросов и стандартной агрегации (сумма/мин/макс) дерево отрезков предпочтительнее. Корневую берут за простоту и универсальность, когда дерево писать дольше или операция нестандартная.

Ваш ответ

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