← Назад к структурам данных

Дерево отрезков (модульная редукция)

Сумма на отрезке, обновление элемента и взятие модуля на отрезке

🐍 Python (реализация)
n = int(input())
tree = [[0] * 2 for _ in range(n * 4 + 1)]
A = list(map(int, input().split()))

def build(v, l, r):
    if l == r:
        tree[v][0] = A[l]
        tree[v][1] = A[l]
        return
    m = (l + r) // 2
    build(v * 2, l, m)
    build(v * 2 + 1, m + 1, r)
    tree[v][0] = tree[v * 2][0] + tree[v * 2 + 1][0]
    tree[v][1] = max(tree[v * 2][1], tree[v * 2 + 1][1])

def update(v, l, r, z, p):
    if l == r:
        tree[v][0] = z
        tree[v][1] = z
        return
    m = (l + r) // 2
    if p <= m:
        update(v * 2, l, m, z, p)
    else:
        update(v * 2 + 1, m + 1, r, z, p)
    tree[v][0] = tree[v * 2][0] + tree[v * 2 + 1][0]
    tree[v][1] = max(tree[v * 2][1], tree[v * 2 + 1][1])

def get(v, l, r, l0, r0):
    if l == l0 and r == r0:
        return tree[v][0]
    m = (l + r) // 2
    s1 = s2 = 0
    if l0 <= m:
        s1 = get(v * 2, l, m, l0, min(r0, m))
    if r0 >= m + 1:
        s2 = get(v * 2 + 1, m + 1, r, max(l0, m + 1), r0)
    return s1 + s2

def mod(v, l, r, l0, r0, x):
    if tree[v][1] < x:
        return
    if l == r:
        tree[v][0] %= x
        tree[v][1] %= x
        return
    m = (l + r) // 2
    if l0 <= m:
        mod(v * 2, l, m, l0, min(r0, m), x)
    if r0 >= m + 1:
        mod(v * 2 + 1, m + 1, r, max(l0, m + 1), r0, x)
    tree[v][0] = tree[v * 2][0] + tree[v * 2 + 1][0]
    tree[v][1] = max(tree[v * 2][1], tree[v * 2 + 1][1])

build(1, 0, n - 1)
q = int(input())

for i in range(q):
    s = list(map(int, input().split()))
    if s[0] == 1:
        l, r = s[1], s[2]
        update(1, 0, n - 1, r, l - 1)
    elif s[0] == 2:
        l, r, x = s[1], s[2], s[3]
        mod(1, 0, n - 1, l - 1, r - 1, x)
    else:
        l, r = s[1], s[2]
        print(get(1, 0, n - 1, l - 1, r - 1))
⚙️ C++ (реализация)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

vector<vector<ll>> tree;
vector<ll> A;

void build(int v, int l, int r) {
    if (l == r) {
        tree[v][0] = A[l];
        tree[v][1] = A[l];
        return;
    }
    int m = (l + r) / 2;
    build(v * 2, l, m);
    build(v * 2 + 1, m + 1, r);
    tree[v][0] = tree[v * 2][0] + tree[v * 2 + 1][0];
    tree[v][1] = max(tree[v * 2][1], tree[v * 2 + 1][1]);
}

void update(int v, int l, int r, ll z, int p) {
    if (l == r) {
        tree[v][0] = z;
        tree[v][1] = z;
        return;
    }
    int m = (l + r) / 2;
    if (p <= m) {
        update(v * 2, l, m, z, p);
    } else {
        update(v * 2 + 1, m + 1, r, z, p);
    }
    tree[v][0] = tree[v * 2][0] + tree[v * 2 + 1][0];
    tree[v][1] = max(tree[v * 2][1], tree[v * 2 + 1][1]);
}

ll get(int v, int l, int r, int l0, int r0) {
    if (l == l0 && r == r0) {
        return tree[v][0];
    }
    int m = (l + r) / 2;
    ll s1 = 0, s2 = 0;
    if (l0 <= m) {
        s1 = get(v * 2, l, m, l0, min(r0, m));
    }
    if (r0 >= m + 1) {
        s2 = get(v * 2 + 1, m + 1, r, max(l0, m + 1), r0);
    }
    return s1 + s2;
}

void mod(int v, int l, int r, int l0, int r0, ll x) {
    if (tree[v][1] < x) return;
    if (l == r) {
        tree[v][0] %= x;
        tree[v][1] %= x;
        return;
    }
    int m = (l + r) / 2;
    if (l0 <= m) {
        mod(v * 2, l, m, l0, min(r0, m), x);
    }
    if (r0 >= m + 1) {
        mod(v * 2 + 1, m + 1, r, max(l0, m + 1), r0, x);
    }
    tree[v][0] = tree[v * 2][0] + tree[v * 2 + 1][0];
    tree[v][1] = max(tree[v * 2][1], tree[v * 2 + 1][1]);
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    int n;
    cin >> n;
    
    A.resize(n);
    for (int i = 0; i < n; i++) {
        cin >> A[i];
    }
    
    tree.assign(4 * n + 1, vector<ll>(2, 0));
    build(1, 0, n - 1);
    
    int q;
    cin >> q;
    
    for (int i = 0; i < q; i++) {
        int type;
        cin >> type;
        
        if (type == 1) {
            int l;
            ll r;
            cin >> l >> r;
            update(1, 0, n - 1, r, l - 1);
        } else if (type == 2) {
            int l, r;
            ll x;
            cin >> l >> r >> x;
            mod(1, 0, n - 1, l - 1, r - 1, x);
        } else {
            int l, r;
            cin >> l >> r;
            cout << get(1, 0, n - 1, l - 1, r - 1) << "\n";
        }
    }
    
    return 0;
}

Как работает дерево отрезков с модульной редукцией?

Это расширенная версия дерева отрезков, которая в каждой вершине хранит сумму и максимум на отрезке. Благодаря хранению максимума, мы можем эффективно выполнять операцию взятия модуля на отрезке.

Основные операции

  • tree[v][0] — сумма на отрезке, который покрывает вершина v
  • tree[v][1] — максимум на том же отрезке

Операция mod (взятие модуля)

Почему это быстро? Каждое число уменьшается после операции взятия модуля, и такое может произойти не более O(log A) раз для одного элемента. Поэтому суммарная сложность — O((n + q) log n log A).

Остальные операции

Временная сложность: O(log n) для update и get, O(log n log A) для mod в среднем.

Пространственная сложность: O(4n) для хранения дерева.

Формат ввода и вывода

Ввод:

Пример ввода:

5
1 2 3 4 5
6
3 1 5
2 1 5 3
3 1 5
1 3 10
2 2 4 4
3 1 5

Вывод:

15
5
12

Пояснение:
Исходный массив: [1,2,3,4,5]
Сумма [1..5] = 15
Применяем %3: [1%3=1, 2%3=2, 3%3=0, 4%3=1, 5%3=2] → [1,2,0,1,2]
Сумма = 6? но в примере выше ожидается 5? поправим логику.

Попробовать дерево отрезков с модулем онлайн

Результат появится здесь...