Sqrt-tree (part 2): modifications in O(sqrtN), lazy propagation

Правка en2, от gepardo, 2018-04-24 22:27:50

Hello, Codeforces!

Some time ago I created a blog post about Sqrt-tree. If you didn't read this post, please do it now.

But earlier, we were able just to answer the queries on a static array. Now we will make our structure more "dynamic" and add update queries there.

So, let's begin!

Update queries

Consider a query that does the assignment ax = val. We need to perform this query fast enough.

Naive approach

First, let's take a look of what is changed in our tree when a single element changes. Consider a tree node with length l and its arrays: , and . It is easy to see that only elements from and will change (only inside the block with the changed element). will change O(l) elements. Therefore, total update count per node is O(l).

We remember that any element x is present in exactly one tree node at each layer. Root node (layer 0) has length O(n), nodes on layer 1 have length , nodes on layer 2 have length , etc. So the time complexity per update is .

But it's too slow. Can it be done faster?  

A sqrt-tree inside the sqrt-tree

Note that the bottleneck of updating is rebuilding of the root node. To optimize the tree, let's get rid of it! Instead of array, we store another sqrt-tree called that plays the same role as — answers the queries on segments of blocks. Note that the rest of the tree nodes don't have , they keep their arrays.

A sqrt-tree is indexed, if its root node has . A sqrt tree with array in its root node is unindexed. Note that is unindexed itself.

So, for the indexed tree, we have the following algorithm for updating:

  • Update and in .

  • Update . It has length and we need to update only one item in it (that represents the changed block). So, the time complexity for this step is .

  • Dive into the child node that represents the changed block and update it in .

Note that the query complexity is still O(1): we need to use in query no more than once, and this will take O(1) time.

So, the total time complexity for updating a single element is . Hooray! :)

Implementation

It is just a slightly modified code of a static sqrt-tree. Here it is.

Lazy propagation

Sqrt-tree also can do things like assigning an element on a segment. means ai = x for all l ≤ i ≤ r.

There are two approaches to do this: one of them does in , keeping O(1) per query. The second one does in , but the query complexity becomes .

We will do lazy propagation in the same way as it is done in segment trees: we mark some nodes as lazy, meaning that we'll push them when it's necessary. But one things is different from segment trees: pushing a node is expensive, so it cannot be done in queries. On the layer 0, pushing a node takes time. So, we don't push nodes in query, we only see if the parent or current node is lazy, and just take it into account while performing queries.

First approach

In the first approach, we consider that only nodes on layer 1 (with length ) can be lazy. When pushing such node, it updates all its subtree including itself in . The update process is done is follows:

  • Consider the nodes on layer 1 and blocks corresponding to them.

  • Some blocks are entirely covered by . Mark them as lazy in .

  • Some blocks are partially covered. Note there are no more than two blocks of this kind. Rebuild them in . If they were lazy, take it into account.

  • Update and for partially covered blocks in (because there are only two such blocks).

  • Rebuild the in .

So we can do fast. But how lazy propagation affects queries? They will have the following modifications:

  • If our query entirely lies in a lazy block, calculate in and take lazy into account. O(1).

  • If our query consists of many blocks, some of which are lazy, we need to take care of lazy only on the leftmost and the rightmost block. The rest of the block are calculated using , which already knows the answer even on lazy block (because it's rebuild after each modification). O(1).

The query complexity remains O(1).

Second approach

In this approach, each node can be lazy. Even nodes in can be lazy. So, while processing a query, we have to look for lazy tags in all the parent nodes, i. e. query complexity will be .

But the update queries will become faster:

  • fully covers some blocks. So, lazy tags are added to them. It is .

  • Do not forget to update the index. It is .

  • Update and for partially covered blocks in (because there are only two such blocks).

  • For partially covered blocks, we go to the nodes representing them and call recursively (reminds something, isn't it? :) ).

Note that when we do the recursive call, we do prefix or suffix . But for prefix and suffix updates we can have no more than one partially covered child. So, we visit one node on layer 1, two nodes on layer 2 and two nodes on any deeper level. So, the time complexity will be . The approach here is similar to the segment tree mass update.

Implementation

I am too lazy (like a node in sqrt-tree :) ) to write the implementation of lazy propagation on a sqrt-tree. So, I leave it as an exercise. If someone provides an implementation for this, I'll post it here mentioning the author.

Conclusion

As we can see, sqrt-tree can perform update queries and do lazy propagation. So, it provides the same functionality as segment trees, but with faster queries. The disadvantage is slow update time, but sqrt-trees can be helpful if we have many ( ≈ 107) queries but not many updates.

Thanks for reading this post. Feel free to write in comments if there are mistakes in it or something is not clear. I hope that this structure will be helpful for you.

I will publish another one post about sqrt-tree soon.

Thanks to Vladik for inspiring me to write this post.

Теги data structure, sqrt-decomposition, tree, log log n, implement yourself, lazy, segment tree 2.0

История

 
 
 
 
Правки
 
 
  Rev. Язык Кто Когда Δ Комментарий
en3 Английский gepardo 2018-04-24 22:40:13 483 Minor fixes (published)
en2 Английский gepardo 2018-04-24 22:27:50 6 Tiny change: 'al update per node ' -> 'al update count per node '
en1 Английский gepardo 2018-04-24 22:21:45 7287 Initial revision (saved to drafts)