段树中的数据映射和延迟传播

时间:2012-05-31 11:45:43

标签: algorithm data-structures segment-tree data-mapping

在整个互联网上看起来只有一篇关于延迟传播的好文章,它是: 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]的读状态。

我在这里面对这个问题。我的分段树实现使得每个节点的值是其左右子节点的总和。因此,当我更新节点的值时,我要更新它的所有父节点。 为了解决逻辑问题,现在我正在更新节点的所有父节点(直到它到达树的根)。 但这会带来性能损失,而我使用分段树进行快速批量更新的整个目的正在被杀死。

任何人都可以解释一下,我在使用分段树时出错吗?

2 个答案:

答案 0 :(得分:3)

我将延迟更新操作与正常更新操作以及如何更改查询操作进行对比。

在正常的单个更新操作中,您更新树的根,然后递归地仅更新树的所需部分(从而为您提供O(log(n))速度)。如果您尝试使用相同的逻辑进行范围更新,您可以看到它如何恶化到O(n)(考虑非常宽的范围,并且看到您将主要需要更新树的两个部分)。

因此,为了克服这个O(n)的想法,只有在你真正需要时才更新树(在以前更新的段上查询/更新,从而使你的更新变得懒惰)。所以这是它的工作原理:

  • 树的创建保持绝对相同。唯一的细微差别是您还创建了一个数组,其中包含有关潜在更新的信息。
  • 当您更新树的节点时,您还要检查是否需要更新它(来自之前的更新操作),如果是 - 您更新它,请将子项标记为将来更新并取消标记节点(懒惰)
  • 当您查询树时,还会检查节点是否需要更新,如果需要更新,请标记它的子节点并在之后取消标记。

以下是更新和查询(解决最大范围查询)的示例。对于full code - check this article

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));
}

答案 1 :(得分:2)

在段树中查询节点时,需要确保其所有祖先和节点本身都已正确更新。您在访问查询节点时执行此操作。

在访问查询节点时,您将遍历从根到查询节点的路径,同时处理所有挂起的更新。由于您需要访问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);
    }
};
相关问题