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

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

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

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

           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].

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

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


person Pranalee    schedule 31.05.2012    source источник
comment
Когда я реализовал дерево сегментов, мне пришлось сделать то же самое. Вы устанавливаете узел [0->4], отмечаете каждый дочерний элемент, а затем обновляете родительские узлы до самого корня.   -  person Justin    schedule 31.05.2012
comment
так что ваше обновление было O (logN)? Кроме того, в дереве сегментов, если мой диапазон составляет от 2 до 7, я обновлю 3 сегмента. рт? [2 2], [3 4] [5 7]   -  person Pranalee    schedule 31.05.2012
comment
O(2*logn) обновление. Да, это самое худшее обновление. Мне было бы любопытно, если кто-нибудь знает лучший подход.   -  person Justin    schedule 31.05.2012
comment
К вашему сведению, злоумышленники могут попытаться украсть вашу информацию с www.spoj.pl. ;(   -  person Charlie 木匠    schedule 10.10.2018


Ответы (2)


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

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

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

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

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

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));
}
person Salvador Dali    schedule 20.01.2015
comment
Спасибо за ссылку на блог. - person Charlie 木匠; 10.10.2018

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

При посещении узла запроса вы проходите путь от корня к узлу запроса, заботясь обо всех ожидающих обновлениях. Поскольку вам нужно посетить O (log N) предков, для любого заданного узла запроса вы делаете только 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);
    }
};
person bloops    schedule 10.07.2012
comment
Это самый лаконичный код, который я видел. Это будет протестировано с проблемой горизонта. Спасибо. - person Charlie 木匠; 11.10.2018
comment
После того, как 81% пройдено, лимит времени превышен. ;( - person Charlie 木匠; 11.10.2018
comment
Проблема кодирования: lintcode.com/problem/the-skyline-problem - person Charlie 木匠; 11.10.2018
comment
Вы можете использовать более простое решение проблемы горизонта. Просто отсортируйте вертикальные линии (начало и конец) и сохраняйте кучу текущих высот, когда вы проводите слева направо. Максимальная высота дает высоту линии горизонта. - person bloops; 11.10.2018
comment
Спасибо за быстрый ответ. Я сделал хеш-кучу + решение линии развертки в первом проходе. Я искал несколько решений. ^_^ Поскольку я только что изучил дерево сегментов, я хотел посмотреть, смогу ли я использовать дерево сегментов для решения других задач. ха-ха. - person Charlie 木匠; 12.10.2018
comment
И есть интересные результаты после отправки в leetcode: leetcode.com/problems/the-skyline-problem/discuss/61313/ . Это упрощенное решение дерева сегментов ленивого распространения в версии Java превзошло 95% представлений. Но точно такая же версия Python превзошла только 5% заявок. Я попытаюсь понять, почему версия Python такая медленная. - person Charlie 木匠; 12.10.2018
comment
U[x], Что означает U? - person Charlie 木匠; 15.10.2018