段树中的延迟传播?

时间:2012-05-23 07:37:30

标签: algorithm segment-tree lazy-propagation

好吧,我试图在Codechef上解决这个Flipping coins问题。用分段树解决它。但是时间限制超过了。我搜索并发现我必须使用懒惰传播。但我无法理解。我的更新功能递归地工作(从上到下)。请提供一些提示或用例子解释。还要指出我必须更改代码的位置。

在翻转硬币时,如果节点值为1,则在更新期间将其更改为0,如果为0,则将其更改为1.

开始和结束是原始数组的限制。树是分段树阵列。

void update(int node, int start, int end,int pos)//pos=position to update
{
    if(start==end) tree[node]=(tree[node]==0) ? 1 : 0;
    else
    {
        int mid=(start+end)/2;
        if(mid>=pos) update(2*node + 1, start, mid, pos);
        else update(2*node + 2, mid+1, end, pos);

        tree[node]=tree[2*node +1] + tree[2*node +2];
    }
}

3 个答案:

答案 0 :(得分:9)

延迟传播意味着仅在需要时进行更新。它是一种允许以渐近时间复杂度O(logN)执行范围更新的技术(这里N是范围)。

假设您要更新范围[0,15],然后更新节点[0,15]并在节点中设置一个标志,表示要更新子节点(使用sentinel值如果没有使用该标志)

可能的压力测试案例:

0 1 100000

0 1 100000

0 1 100000 ...重复Q次(其中Q = 99999) 第100000个查询将是

1 1 100000

在这种情况下,大多数实施人员只会翻转100000个硬币99999次,以便在最后和超时时回答一个简单的查询。

使用延迟传播,您只需要翻转节点[0,100000] 99999次并设置/取消设置其子项将要更新的标志。当询问实际查询本身时,您开始遍历其子项并开始翻转它们,将标志向下推并取消设置父标志。

哦,确保你正在使用正确的I / O例程(scanf和printf而不是cin和cout,如果它的c ++) 希望这能让您了解延迟传播的含义。 更多信息 : http://www.spoj.pl/forum/viewtopic.php?f=27&t=8296

答案 1 :(得分:2)

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

在正常的单个更新操作中,您更新树的根,然后递归地仅更新树的所需部分(从而为您提供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));
}

答案 2 :(得分:1)

要将5..15标记为“是”,以下是您所需要的全部内容。操作中只涉及7个节点,远小于不使用延迟传播的情况。可以证明,最多可能涉及2logn - 1个节点,其中n是范围。

         0..31u
         /    \
      0..15u 16..31n
      /    \
   0..8u  9..15y
   /   \
0..4n  5..8y 

(u: unknown, look deeper; y: yes; n: no)

没有延迟传播,段树并不比普通数组好。

有关如何实施的更多详细信息由您自行决定。你应该自己解决。

相关问题