![]() |
VOOZH | about |
In competitive programming, mastering range operations and understanding lazy propagation techniques is a crucial skill set for efficiently handling complex problems involving large datasets. Range operations involve performing actions, such as queries or updates, over a specific range of elements in a data structure, offering a powerful tool for solving algorithmic challenges.
Table of Content
The array range query problem can be defined as follows:
Given an array of numbers, the array range query problem is to build a data structure that can efficiently answer queries of a particular type mentioned in terms of an interval of the indices.
The specific query can be of type – maximum element in the given range, most frequent element in the given range or queries like these.
Segment Tree is a data structure allows answering range queries and allowing modifying point updates efficiently. This includes finding sum of a range, minimum/maximum of a range, GCD of range, etc. in O(log n) time while modifying the array by updating one element. The Updates work in O(log n) time.
Segment Tree works well with modifications that affect single element of the array. But when modifications have to be done to a range (l to r), by updating each element of the array in given range using the segment tree, complexity would be O(N*log N). This is where Lazy Propagation is required.
Lazy propagation is a technique that allows modification queries to a segment of contiguous elements and perform the query at the same time in O(log n) complexity.
Let n be the size of array in which we are going to perform range updates and query. Let b be the segment tree of size 4*n.
Let's build the segment tree for lazy propagation for the following problem: Given an array a of size n, the task is to process the following types of queries.
The
buildfunction recursively constructs a segment tree with lazy propagation. It recursively builds the segment tree by dividing the array into halves until reaching individual elements (leaves). At each step, the function initializes the value of the segment tree nodeb[v]with the sum of its children. The lazy propagation feature is implemented by initializingb[v]to 0, indicating that no updates have been propagated yet. The tree is constructed in a top-down manner, and each node represents a segment of the original array. The time complexity of this operation is O(n).
Below is the implementation of above approach:
For example, the segment tree after the build operation for array a=[] will look something like this:
To add the value val to the elements on the segment [l..r], we use recursive traversal, similar to traversal we used to find the sum on a segment. We will stop recursion in the following cases:
- The segment corresponding to the current node does not intersect with the segment [l..r].
- The segment corresponding to the current node lies entirely in the segment [l..r].
Apart from the above two cases, we will make two recursive calls as we do when we have to find the sum on a segment. The time complexity of this operation is O(log n).
Below is the implementation of above approach:
To find the value of the i-th element of an array it can be observed that ai was changed only by those operations, those segments contain the index i. which are nothing but nodes on the path from the i-th leaf to the root of the segment tree. So, we add all elements along the path from leaf to root. The time complexity of this operation is O(log n).
Below is the implementation of above approach:
Given an array of n elements, initialized with zero. The task is two process two types of queries:
The idea is to apply the update operation in such a way that operations are deeper in the tree than the newer ones so that when we have a get operation, we can simply go from leaf to root, and newer operations will be encountered after the older operations.
To implement this we make a lazy update. A visited node will mean, that every element of the corresponding segment is assigned the operation with value equal to the value of that node. Suppose we have to apply max operation to whole array with value val, the value is placed at the root of the tree. Now suppose the second operation says apply max to first part of the array. To solve this, we first visit the root vertex, since it has been already visited and assigned an operation, we assign that operation to its left and right child and then apply the current operation to left child again. In this way no Information is lost and also the older operations will be stored at a deeper level. The time complexity of both get and update operation will be O(log n).
Below is the implementation of above approach:
Given an array of n elements, initialized with zero. The task is two process two types of queries:
The idea is to apply the update operation in such a way that operations are deeper in the tree than the newer operations.
The only difference here is that in get operation when we go from root to leaf, if a node is visited we can simply return the value of that node and there is no need to go deeper, since the newer operations are stored at the higher level. The time complexity of both gets and update operation will be O(log n).
Below is the implementation of above approach:
42
Article Link | Problem Link |
|---|---|
In conclusion, the utilization of Range Operations and Lazy Propagation techniques in competitive programming provides an efficient and optimized approach to deal with a variety of problems involving range queries and updates. These techniques not only enhance the speed of algorithmic solutions but also reduce unnecessary computations, making them particularly valuable in time-sensitive competitive environments. By employing lazy propagation, we can defer updates and minimize redundant calculations, thereby improving the overall performance of algorithms dealing with range operations.