3

好吧,我试图在 Codechef 上解决这个翻转硬币的问题。正在用段树解决它。但获得时间限制超过。我搜索了一下,发现必须使用惰性传播。但我无法理解。我的更新功能递归工作(从上到下)。请给出一些提示或举例说明。还要指出我必须更改代码的地方。

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

start 和 end 是原始数组的限制。tree 是段树数组。

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];
    }
}
4

3 回答 3

9

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

假设您要更新范围 [0,15],然后您更新节点 [0,15] 并在节点中设置一个标志,表示要更新它的子节点(使用标记值,以防标志不是用过)

可能的压力测试案例:

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

于 2012-05-24T10:05:43.210 回答
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));
}
于 2015-01-20T01:29:14.030 回答
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)

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

如何实施的更多细节留给您。你应该自己解决。

于 2012-05-24T08:40:44.023 回答