Open In App

Range Operations and Lazy Propagation for Competitive Programming

Last Updated : 23 Apr, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

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.

What is a range query?

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.

What is a Segment Tree?

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.

Why Segment Tree is not optimal for Range Operations (updates):

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 for Range Operations:

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.

  • The root is present at b[1], which represents the whole array a[0, 1 ….. n-1]. Similarly, value at each index of segment tree b, represents value of a segment from l to r in array a which can be the sum, minimum/maximum, gcd ,etc.
  • Each node of segment tree has two children. Child of node at i-th index are present at 2*i (left) and 2*i+1 (right) index.
  • Leaf nodes of segment tree b represent every value of array a.

Implementation for Lazy Propagation:

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.

  • update(l, r, x): add x to all ai(l ≤ i < r),
  • get(i): get the value of ai.

Building a Segment Tree:

The build function 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 node b[v] with the sum of its children. The lazy propagation feature is implemented by initializing b[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:

C++
void build(vector<int>& a, vector<int>& b, int v, int tl, int tr) {
    if (tl == tr) {
        b[v] = a[tl];
        return;
    }
    int tm = (tl + tr) / 2;
    build(a, b, v * 2, tl, tm);
    build(a, b, v * 2 + 1, tm + 1, tr);
    b[v] = 0;
}
Java
public class SegmentTree {

    public static void build(int[] a, int[] b, int v, int tl, int tr) {
        // If the range has only one element, assign the value to the corresponding position in b and return
        if (tl == tr) {
            b[v] = a[tl];
            return;
        }

        // Calculate the middle index of the range
        int tm = (tl + tr) / 2;

        // Recursively build left and right subtrees
        build(a, b, v * 2, tl, tm);
        build(a, b, v * 2 + 1, tm + 1, tr);

        // Set the value of the current node 'v' in b to 0 (You may need to adjust this assignment based on your use case)
        b[v] = 0;
    }

    public static void main(String[] args) {
        int[] a = {3, 5, 1, 7, 2};
        int[] b = new int[4 * a.length]; // 'b' is initialized with zeros

        // Build the segment tree
        build(a, b, 1, 0, a.length - 1);

        // The resulting 'b' array will contain the segment tree structure based on array 'a'
        for (int value : b) {
            System.out.print(value + " ");
        }
    }
}
Python
class SegmentTree:
    @staticmethod
    def build(a, b, v, tl, tr):
        """
        Function to build the segment tree recursively.

        Parameters:
            a (list): The original array.
            b (list): The segment tree array.
            v (int): Current node in the segment tree.
            tl (int): Left index of the current segment.
            tr (int): Right index of the current segment.
        """
        # If the range has only one element, assign the value to the corresponding position in b and return
        if tl == tr:
            b[v] = a[tl]
            return

        # Calculate the middle index of the range
        tm = (tl + tr) // 2

        # Recursively build left and right subtrees
        SegmentTree.build(a, b, v * 2, tl, tm)
        SegmentTree.build(a, b, v * 2 + 1, tm + 1, tr)

        # Set the value of the current node 'v' in b to 0 (You may need to adjust this assignment based on your use case)
        b[v] = 0

    @staticmethod
    def main():
        a = [3, 5, 1, 7, 2]
        b = [0] * (4 * len(a))  # 'b' is initialized with zeros

        # Build the segment tree
        SegmentTree.build(a, b, 1, 0, len(a) - 1)

        # The resulting 'b' array will contain the segment tree structure based on array 'a'
        for value in b:
            print value,


# Call the main method
SegmentTree.main()
C#
using System;

public class SegmentTree
{
    public static void Build(int[] a, int[] b, int v, int tl, int tr)
    {
        // If the range has only one element, assign the value to the corresponding position in b and return
        if (tl == tr)
        {
            b[v] = a[tl];
            return;
        }

        // Calculate the middle index of the range
        int tm = (tl + tr) / 2;

        // Recursively build left and right subtrees
        Build(a, b, v * 2, tl, tm);
        Build(a, b, v * 2 + 1, tm + 1, tr);

        // Set the value of the current node 'v' in b to 0 (You may need to adjust this assignment based on your use case)
        b[v] = 0;
    }

    public static void Main(string[] args)
    {
        int[] a = { 3, 5, 1, 7, 2 };
        int[] b = new int[4 * a.Length]; // 'b' is initialized with zeros

        // Build the segment tree
        Build(a, b, 1, 0, a.Length - 1);

        // The resulting 'b' array will contain the segment tree structure based on array 'a'
        foreach (int value in b)
        {
            Console.Write(value + " ");
        }
    }
}
// This class represents a Segment Tree data structure in C#.
Javascript
function build(a, b, v, tl, tr) {
    // If the range has only one element, assign the value to the corresponding position in b and return
    if (tl === tr) {
        b[v] = a[tl];
        return;
    }
    
    // Calculate the middle index of the range
    let tm = Math.floor((tl + tr) / 2);
    
    // Recursively build left and right subtrees
    build(a, b, v * 2, tl, tm);
    build(a, b, v * 2 + 1, tm + 1, tr);
    
    // Set the value of the current node 'v' in b to 0 (You may need to adjust this assignment based on your use case)
    b[v] = 0;
}

// Example usage:
let a = [3, 5, 1, 7, 2];
let b = new Array(4 * a.length).fill(0); // 'b' is initialized with zeros

// Build the segment tree
build(a, b, 1, 0, a.length - 1);

// The resulting 'b' array will contain the segment tree structure based on array 'a'
console.log(b);

For example, the segment tree after the build operation for array a=[] will look something like this:

Update Operation:

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:

  1. The segment corresponding to the current node does not intersect with the segment [l..r].
  2. 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:

C++
void update(vector<int>& b, int v, int tl, int tr, int l,
            int r, int val)
{
    if (l > r)
        return;
    if (l == tl && r == tr) {
        b[v] += val;
        return;
    }
    int tm = (tl + tr) / 2;
    update(b,v * 2, tl, tm, l, min(r, tm), val);
    update(b,v * 2 + 1, tm + 1, tr, max(l, tm + 1), r, val);
}
Java
import java.util.*;

public class SegmentTree {
    void update(int[] b, int v, int tl, int tr, int l, int r, int val) {
        if (l > r)
            return;
        if (l == tl && r == tr) {
            b[v] += val;
            return;
        }
        int tm = (tl + tr) / 2;
        update(b, v * 2, tl, tm, l, Math.min(r, tm), val);
        update(b, v * 2 + 1, tm + 1, tr, Math.max(l, tm + 1), r, val);
    }

    public static void main(String[] args) {
        // Example usage
        int n = 8;
        int[] b = new int[4 * n]; // Segment tree array
        SegmentTree segmentTree = new SegmentTree();
        segmentTree.update(b, 1, 0, n - 1, 3, 6, 5); // Example update
    }
}
Python3
def update(b, v, tl, tr, l, r, val):
    if l > r:
        return
    if l == tl and r == tr:
        b[v] += val
        return
    tm = (tl + tr) // 2
    update(b, v * 2, tl, tm, l, min(r, tm), val)
    update(b, v * 2 + 1, tm + 1, tr, max(l, tm + 1), r, val)
JavaScript
class SegmentTree {
    update(b, v, tl, tr, l, r, val) {
        if (l > r)
            return;
        if (l === tl && r === tr) {
            b[v] += val;
            return;
        }
        let tm = Math.floor((tl + tr) / 2);
        this.update(b, v * 2, tl, tm, l, Math.min(r, tm), val);
        this.update(b, v * 2 + 1, tm + 1, tr, Math.max(l, tm + 1), r, val);
    }

    static main() {
        // Example usage
        let n = 8;
        let b = new Array(4 * n).fill(0); // Segment tree array
        let segmentTree = new SegmentTree();
        segmentTree.update(b, 1, 0, n - 1, 3, 6, 5); // Example update
    }
}

SegmentTree.main();
//This code is contributed by abdulaslam

Get Operation:

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:

C++
int get(vector<int>& b, int v, int tl, int tr, int pos) {
    if (tl == tr)
        return b[v];
    int tm = (tl + tr) / 2;
    if (pos <= tm)
        return b[v] + get(b, v * 2, tl, tm, pos);
    else
        return b[v] + get(b, v * 2 + 1, tm + 1, tr, pos);
}
JavaScript
function get(b, v, tl, tr, pos) {
    if (tl === tr)
        return b[v];
    let tm = Math.floor((tl + tr) / 2);
    if (pos <= tm)
        return b[v] + get(b, v * 2, tl, tm, pos);
    else
        return b[v] + get(b, v * 2 + 1, tm + 1, tr, pos);
}

Use cases of Lazy Propagation in Competitive Programming:

1. Applying MAX to Segment:

Given an array of n elements, initialized with zero. The task is two process two types of queries:

  • for all i from l to r do the operation ai = max(ai, val),
  • find the current value of element i.

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:

C++
typedef long long ll;

void spread(vector<ll>& b, vector<ll>& vis, ll v) {
    if (vis[v]) {
        b[v * 2] = max(b[v], b[v * 2]);
        b[v * 2 + 1] = max(b[v], b[v * 2 + 1]);
        vis[v * 2] = 1;
        vis[v * 2 + 1] = 1;
        vis[v] = 0;
    }
}

void update(vector<ll>& b, vector<ll>& vis, ll v, ll tl, ll tr, ll l, ll r, ll val) {
    if (l > r)
        return;
    if (l == tl && r == tr) {
        b[v] = max(val, b[v]);
        vis[v] = 1;
        return;
    }
    spread(b, vis, v);
    ll tm = (tl + tr) / 2;
    update(b, vis, v * 2, tl, tm, l, min(r, tm), val);
    update(b, vis, v * 2 + 1, tm + 1, tr, max(l, tm + 1), r, val);
}

ll get(vector<ll>& b, ll v, ll tl, ll tr, ll pos) {
    if (tl == tr)
        return b[v];
    ll tm = (tl + tr) / 2;
    if (pos <= tm)
        return max(get(b, v * 2, tl, tm, pos), b[v]);
    else
        return max(get(b, v * 2 + 1, tm + 1, tr, pos), b[v]);
}
Java
import java.util.*;

public class Main {

    // Defining the function 'spread'
    public static void spread(int[] b, int[] vis, int v) {
        // If 'v' is visited
        if (vis[v] == 1) {
            // Updating the values in the array 'b'
            b[v * 2] = Math.max(b[v], b[v * 2]);
            b[v * 2 + 1] = Math.max(b[v], b[v * 2 + 1]);
            // Marking the elements as visited
            vis[v * 2] = 1;
            vis[v * 2 + 1] = 1;
            vis[v] = 0;
        }
    }

    // Defining the function 'update'
    public static void update(int[] b, int[] vis, int v, int tl, int tr, int l, int r, int val) {
        // If 'l' is greater than 'r', then return
        if (l > r) {
            return;
        }
        // If 'l' is equal to 'tl' and 'r' is equal to 'tr'
        if (l == tl && r == tr) {
            // Updating the value in the array 'b'
            b[v] = Math.max(val, b[v]);
            // Marking the element as visited
            vis[v] = 1;
            return;
        }
        // Calling the function 'spread'
        spread(b, vis, v);
        int tm = (tl + tr) / 2;
        // Recursive calls to the function 'update'
        update(b, vis, v * 2, tl, tm, l, Math.min(r, tm), val);
        update(b, vis, v * 2 + 1, tm + 1, tr, Math.max(l, tm + 1), r, val);
    }

    // Defining the function 'get'
    public static int get(int[] b, int v, int tl, int tr, int pos) {
        // If 'tl' is equal to 'tr', then return the value at index 'v' in the array 'b'
        if (tl == tr) {
            return b[v];
        }
        int tm = (tl + tr) / 2;
        // If 'pos' is less than or equal to 'tm'
        if (pos <= tm) {
            return Math.max(get(b, v * 2, tl, tm, pos), b[v]);
        } else {
            return Math.max(get(b, v * 2 + 1, tm + 1, tr, pos), b[v]);
        }
    }

    public static void main(String[] args) {
        // Example usage
        int[] b = new int[100]; // assuming the size of array 'b'
        int[] vis = new int[100]; // assuming the size of array 'vis'
        // Example update
        update(b, vis, 1, 0, 10, 2, 5, 8);
        // Example get
        int result = get(b, 1, 0, 10, 3);
        System.out.println("Result: " + result);
    }
}
//This code is contributed by Utkarsh.
Python3
# Importing the required library
from typing import List

# Defining the function 'spread'
def spread(b: List[int], vis: List[int], v: int) -> None:
    # If 'v' is visited
    if vis[v]:
        # Updating the values in the list 'b'
        b[v * 2] = max(b[v], b[v * 2])
        b[v * 2 + 1] = max(b[v], b[v * 2 + 1])
        # Marking the elements as visited
        vis[v * 2] = 1
        vis[v * 2 + 1] = 1
        vis[v] = 0

# Defining the function 'update'
def update(b: List[int], vis: List[int], v: int, tl: int, tr: int, l: int, r: int, val: int) -> None:
    # If 'l' is greater than 'r', then return
    if l > r:
        return
    # If 'l' is equal to 'tl' and 'r' is equal to 'tr'
    if l == tl and r == tr:
        # Updating the value in the list 'b'
        b[v] = max(val, b[v])
        # Marking the element as visited
        vis[v] = 1
        return
    # Calling the function 'spread'
    spread(b, vis, v)
    tm = (tl + tr) // 2
    # Recursive calls to the function 'update'
    update(b, vis, v * 2, tl, tm, l, min(r, tm), val)
    update(b, vis, v * 2 + 1, tm + 1, tr, max(l, tm + 1), r, val)

# Defining the function 'get'
def get(b: List[int], v: int, tl: int, tr: int, pos: int) -> int:
    # If 'tl' is equal to 'tr', then return the value at index 'v' in the list 'b'
    if tl == tr:
        return b[v]
    tm = (tl + tr) // 2
    # If 'pos' is less than or equal to 'tm'
    if pos <= tm:
        return max(get(b, v * 2, tl, tm, pos), b[v])
    else:
        return max(get(b, v * 2 + 1, tm + 1, tr, pos), b[v])

2.Assignment to a Segment:

Given an array of n elements, initialized with zero. The task is two process two types of queries:

  • for all i from l to r do the operation ai=val,
  • find the current value of element i.

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:

C++
#include <iostream>
#include <vector>

using namespace std;

class SegmentTree {
private:
    vector<int> b, vis;
    int size;

    void spread(int v) {
        if (vis[v] == 1) {
            b[v * 2] = b[v];
            b[v * 2 + 1] = b[v];
            vis[v * 2] = 1;
            vis[v * 2 + 1] = 1;
            vis[v] = 0;
        }
    }

public:
    SegmentTree(int size) : size(size) {
        b.resize(4 * size);
        vis.resize(4 * size);
    }

    void update(int v, int tl, int tr, int l, int r, int val) {
        if (l > r || v >= 4 * size) return;
        if (l <= tl && r >= tr) {
            b[v] = val;
            vis[v] = 1;
            return;
        }
        spread(v);
        int tm = (tl + tr) / 2;
        update(v * 2, tl, tm, l, min(r, tm), val);
        update(v * 2 + 1, tm + 1, tr, max(l, tm + 1), r, val);
    }

    int get(int v, int tl, int tr, int pos) {
        if (tl == tr) return b[v];
        int tm = (tl + tr) / 2;
        if (pos <= tm) {
            if (vis[v] == 1) return b[v];
            else return get(v * 2, tl, tm, pos);
        } else {
            if (vis[v] == 1) return b[v];
            else return get(v * 2 + 1, tm + 1, tr, pos);
        }
    }
};

int main() {
    int size = 10;
    SegmentTree segmentTree(size);

    segmentTree.update(1, 0, size - 1, 2, 5, 42);

    int result = segmentTree.get(1, 0, size - 1, 3);
    cout << result << endl; // Output: 42

    return 0;
}
Java
// Implementation of a Segment Tree class
class SegmentTree {
    // Arrays to store segment tree nodes and visibility flags
    int[] b, vis;
    int size;

    // Constructor to initialize the segment tree with given size
    public SegmentTree(int size) {
        this.size = size;
        this.b = new int[4 * size]; // Update array size to accommodate all possible nodes
        this.vis = new int[4 * size]; // Update array size to accommodate all possible nodes
    }

    // Helper function to spread values to child nodes
    private void spread(int v) {
        if (vis[v] == 1) {
            // Spread the current value to both child nodes
            b[v * 2] = b[v];
            b[v * 2 + 1] = b[v];
            // Mark both child nodes as visible
            vis[v * 2] = 1;
            vis[v * 2 + 1] = 1;
            // Mark the current node as not visible
            vis[v] = 0;
        }
    }

    // Update function to modify values in the segment tree
    public void update(int v, int tl, int tr, int l, int r, int val) {
        // If the range is invalid, return
        if (l > r || v >= 4 * size) return;

        // If the current node covers the entire range, update its value and visibility
        if (l <= tl && r >= tr) {
            b[v] = val;
            vis[v] = 1;
            return;
        }

        // Spread the current value to child nodes
        spread(v);

        // Calculate the midpoint of the range
        int tm = (tl + tr) / 2;

        // Recursively update the left and right child nodes
        update(v * 2, tl, tm, l, Math.min(r, tm), val);
        update(v * 2 + 1, tm + 1, tr, Math.max(l, tm + 1), r, val);
    }

    // Get function to retrieve values from the segment tree
    public int get(int v, int tl, int tr, int pos) {
        // If the range has reduced to a single point, return the value at that point
        if (tl == tr) return b[v];

        // Calculate the midpoint of the range
        int tm = (tl + tr) / 2;

        // If the position is in the left half, recursively get the value from the left child
        if (pos <= tm) {
            // If the current node is marked as visible, return its value
            if (vis[v] == 1) return b[v];
            // Otherwise, continue searching in the left child
            else return get(v * 2, tl, tm, pos);
        } 
        // If the position is in the right half, recursively get the value from the right child
        else {
            // If the current node is marked as visible, return its value
            if (vis[v] == 1) return b[v];
            // Otherwise, continue searching in the right child
            else return get(v * 2 + 1, tm + 1, tr, pos);
        }
    }
}

// Driver Code
public class Main {
    public static void main(String[] args) {
        int size = 10;
        SegmentTree segmentTree = new SegmentTree(size);

        segmentTree.update(1, 0, size - 1, 2, 5, 42);

        int result = segmentTree.get(1, 0, size - 1, 3);
        System.out.println(result); // Output: 42
    }
}
Python3
class SegmentTree:
    def __init__(self, size):
        self.b = [0] * (4 * size)  # Initialize array for segment tree values
        self.vis = [0] * (4 * size)  # Initialize array for marking segments as visited
        self.size = size

    def spread(self, v):
        """Spread the value of a parent node to its children if necessary."""
        if self.vis[v] == 1:
            self.b[v * 2] = self.b[v]
            self.b[v * 2 + 1] = self.b[v]
            self.vis[v * 2] = 1
            self.vis[v * 2 + 1] = 1
            self.vis[v] = 0

    def update(self, v, tl, tr, l, r, val):
        """Update the segment tree with the given range and value."""
        if l > r or v >= 4 * self.size:
            return
        if l <= tl and r >= tr:
            self.b[v] = val
            self.vis[v] = 1
            return
        self.spread(v)
        tm = (tl + tr) // 2
        self.update(v * 2, tl, tm, l, min(r, tm), val)
        self.update(v * 2 + 1, tm + 1, tr, max(l, tm + 1), r, val)

    def get(self, v, tl, tr, pos):
        """Get the value at the given position in the segment tree."""
        if tl == tr:
            return self.b[v]
        tm = (tl + tr) // 2
        if pos <= tm:
            if self.vis[v] == 1:
                return self.b[v]
            else:
                return self.get(v * 2, tl, tm, pos)
        else:
            if self.vis[v] == 1:
                return self.b[v]
            else:
                return self.get(v * 2 + 1, tm + 1, tr, pos)

if __name__ == "__main__":
    size = 10
    segmentTree = SegmentTree(size)

    segmentTree.update(1, 0, size - 1, 2, 5, 42)

    result = segmentTree.get(1, 0, size - 1, 3)
    print(result)  # Output: 42
#this code is contributed  by Utkarsh
Javascript
// Implementation of a Segment Tree class
class SegmentTree {
    // Constructor to initialize the segment tree with given size
    constructor(size) {
        // Arrays to store segment tree nodes and visibility flags
        this.b = new Array(2 * size).fill(0);
        this.vis = new Array(2 * size).fill(0);
    }

    // Helper function to spread values to child nodes
    spread(v) {
        if (this.vis[v]) {
            // Spread the current value to both child nodes
            this.b[v * 2] = this.b[v];
            this.b[v * 2 + 1] = this.b[v];
            // Mark both child nodes as visible
            this.vis[v * 2] = 1;
            this.vis[v * 2 + 1] = 1;
            // Mark the current node as not visible
            this.vis[v] = 0;
        }
    }

    // Update function to modify values in the segment tree
    update(v, tl, tr, l, r, val) {
        // If the range is invalid, return
        if (l > r) return;

        // If the current node covers the entire range, update its value and visibility
        if (l === tl && r === tr) {
            this.b[v] = val;
            this.vis[v] = 1;
            return;
        }

        // Spread the current value to child nodes
        this.spread(v);

        // Calculate the midpoint of the range
        const tm = Math.floor((tl + tr) / 2);

        // Recursively update the left and right child nodes
        this.update(v * 2, tl, tm, l, Math.min(r, tm), val);
        this.update(v * 2 + 1, tm + 1, tr, Math.max(l, tm + 1), r, val);
    }

    // Get function to retrieve values from the segment tree
    get(v, tl, tr, pos) {
        // If the range has reduced to a single point, return the value at that point
        if (tl === tr) return this.b[v];

        // Calculate the midpoint of the range
        const tm = Math.floor((tl + tr) / 2);

        // If the position is in the left half, recursively get the value from the left child
        if (pos <= tm) {
            // If the current node is marked as visible, return its value
            if (this.vis[v]) return this.b[v];
            // Otherwise, continue searching in the left child
            else return this.get(v * 2, tl, tm, pos);
        } 
        // If the position is in the right half, recursively get the value from the right child
        else {
            // If the current node is marked as visible, return its value
            if (this.vis[v]) return this.b[v];
            // Otherwise, continue searching in the right child
            else return this.get(v * 2 + 1, tm + 1, tr, pos);
        }
    }
}

// Driver Code
const size = 10;
const segmentTree = new SegmentTree(size);

segmentTree.update(1, 0, size - 1, 2, 5, 42);

const result = segmentTree.get(1, 0, size - 1, 3);
console.log(result); 

Output
42

Practice Problems on Lazy Propagation for Competitive Programming:

Article Link

Problem Link

Easy Task

Solve Now

Nitika and her queries

Solve Now

Range Minimum Query

Solve Now

Smallest Subarray GCD

Solve Now

Akku and Arrays

Solve Now

Maximum Prefix Sum for a given range

Solve Now

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.



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads