Отображение данных и ленивое распространение в дереве сегментов

Похоже, есть только одна хорошая статья о ленивом распространении в Сегментном Дереве на весь интернет, и это: http://www.spoj.pl/forum/viewtopic.php?f=27&t=8296

Я понял концепцию обновления только запроса node и маркировки его дочернего элемента. Но мой вопрос заключается в том, что если я сначала запрошу дочерний node и родительский node.

В этом дереве (наряду с расположением в массиве кучи)

           0->[0 9]
      1->[0 4]    2->[5 9]
   3->[0 2] 4->[3 4]  5->[5 7] 6->[8 9]
 .....................................

Первый запрос, если я обновляю [0 4], его данные будут изменены, а его дочерний элемент будет помечен. Второй запрос - состояние чтения сегмента [0 9].

Здесь я столкнулся с проблемой. Моя реализация дерева сегментов такова, что значение каждого node является суммой его левого и правого дочерних элементов. Поэтому, когда я обновляю значение node, я должен обновить его всех родителей. Чтобы исправить логическую проблему, теперь я обновляю весь родительский элемент node (пока он не достигнет корня дерева). Но это приводит к сбору данных, и вся моя цель использования дерева сегментов для быстрого пакетного обновления убивается.

Может кто-нибудь объяснить, где я ошибаюсь в использовании дерева сегментов?

Ответы

Ответ 1

Я сравню ленивую операцию обновления с обычной операцией обновления и как это изменит операцию запроса.

В обычной операции с одним обновлением вы обновляете корень дерева, а затем рекурсивно обновляете только нужную часть дерева (тем самым предоставляя вам скорость O(log(n))). Если вы попытаетесь использовать ту же логику для обновления диапазона, вы можете увидеть, как она может ухудшиться до O(n) (рассмотрите очень широкие диапазоны и посмотрите, что вам в основном нужно будет обновлять обе части дерева).

Итак, для преодоления этой идеи O(n) необходимо обновить дерево только тогда, когда оно вам действительно нужно (запрос/обновление в сегменте, который был ранее обновлен, что делает ваши обновления ленивыми). Итак, вот как это работает:

  • создание дерева остается абсолютно одинаковым. Единственное незначительное отличие состоит в том, что вы также создаете массив, который содержит информацию о потенциальных обновлениях.
  • когда вы обновляете node дерева, вы также проверяете, нужно ли его обновлять (из предыдущей операции обновления), а если оно есть - вы обновляете его, отмечаете, что дети будут обновляться в будущем и отмените node (ленивый)
  • когда вы запрашиваете дерево, вы также проверяете, нужно ли обновлять node, и если это обновление, отметьте его потом и отмените его после.

Ниже приведен пример обновления и запроса (решение запроса максимального диапазона). Для полный код - проверьте эту статью.

void update_tree(int node, int a, int b, int i, int j, int value) {
    if(lazy[node] != 0) { // This node needs to be updated
        tree[node] += lazy[node]; // Update it
        if(a != b) {
            lazy[node*2] += lazy[node]; // Mark child as lazy
            lazy[node*2+1] += lazy[node]; // Mark child as lazy
        }
        lazy[node] = 0; // Reset it
    }

    if(a > b || a > j || b < i) // Current segment is not within range [i, j]
        return;

    if(a >= i && b <= j) { // Segment is fully within range
        tree[node] += value;
        if(a != b) { // Not leaf node
            lazy[node*2] += value;
            lazy[node*2+1] += value;
        }
        return;
    }

    update_tree(node*2, a, (a+b)/2, i, j, value); // Updating left child
    update_tree(1+node*2, 1+(a+b)/2, b, i, j, value); // Updating right child
    tree[node] = max(tree[node*2], tree[node*2+1]); // Updating root with max value
}

и запрос:

int query_tree(int node, int a, int b, int i, int j) {
    if(a > b || a > j || b < i) return -inf; // Out of range

    if(lazy[node] != 0) { // This node needs to be updated
        tree[node] += lazy[node]; // Update it
        if(a != b) {
            lazy[node*2] += lazy[node]; // Mark child as lazy
            lazy[node*2+1] += lazy[node]; // Mark child as lazy
        }
        lazy[node] = 0; // Reset it
    }

    if(a >= i && b <= j) // Current segment is totally within range [i, j]
        return tree[node];

    return max(query_tree(node*2, a, (a+b)/2, i, j), query_tree(1+node*2, 1+(a+b)/2, b, i, j));
}

Ответ 2

Когда вы запрашиваете node в дереве сегментов, вы должны убедиться, что все его предки и сам node правильно обновлены. Вы делаете это при посещении запроса node (s).

При посещении запроса node вы перемещаете путь от корня к запросу node, сохраняя при этом все ожидающие обновления. Поскольку есть предки O (log N), которые вам нужно посетить, для любого заданного запроса node вы выполняете только O (log N).

Вот мой код для дерева сегментов с ленивым распространением.

// interval updates, interval queries (lazy propagation)  
const int SN = 256;  // must be a power of 2

struct SegmentTree {

    // T[x] is the (properly updated) sum of indices represented by node x
    // U[x] is pending increment for _each_ node in the subtree rooted at x 
    int T[2*SN], U[2*SN];

    SegmentTree() { clear(T,0), clear(U,0); }

    // increment every index in [ia,ib) by incr 
    // the current node is x which represents the interval [a,b)
    void update(int incr, int ia, int ib, int x = 1, int a = 0, int b = SN) { // [a,b)
        ia = max(ia,a), ib = min(ib,b); // intersect [ia,ib) with [a,b)
        if(ia >= ib) return;            // [ia,ib) is empty 
        if(ia == a && ib == b) {        // We push the increment to 'pending increments'
            U[x] += incr;               // And stop recursing
            return; 
        }
        T[x] += incr * (ib - ia);          // Update the current node
        update(incr,ia,ib,2*x,a,(a+b)/2);  // And push the increment to its children
        update(incr,ia,ib,2*x+1,(a+b)/2, b);
    }

    int query(int ia, int ib, int x = 1, int a = 0, int b = SN) {
        ia = max(ia,a), ib = min(ib,b); //  intersect [ia,ib) with [a,b)
        if(ia >= ib) return 0;          // [ia,ib) is empty 
        if(ia == a && ib == b) 
            return U[x]*(b - a) + T[x];

        T[x] += (b - a) * U[x];           // Carry out the pending increments
        U[2*x] += U[x], U[2*x+1] += U[x]; // Push to the childrens' 'pending increments'
        U[x] = 0;

        return query(ia,ib,2*x,a,(a+b)/2) + query(ia,ib,2*x+1,(a+b)/2,b);
    }
};