Segment TreeLazy PropagationSGT VisualizerAbout

What is a Segment Tree?

A Segment Tree is a tree-based data structure used to efficiently handle range-based queries on an array. It allows us to query information (such as sum, minimum, or maximum) over a subarray and also update elements efficiently.

In simple terms, a Segment Tree is used when:

Why do we need a Segment Tree?

Consider an array: arr = [a₁, a₂, a₃, a₄, …, aₙ]

Suppose we have two types of queries:

Problem with Prefix Sum

Using a prefix sum array, we can answer range sum queries in O(1) time. However, when an update happens:

If there are many update queries, the total time complexity becomesO(q · n), which is not feasible for large inputs (e.g., n = 10⁶).

This leads to TLE (Time Limit Exceeded).

How Segment Tree Solves This

A Segment Tree solves this problem efficiently by:

So, even if we have many queries:

Conclusion

That is why Segment Trees are widely used in competitive programming and high-performance systems.

Structure of the Segment Tree

We can take a divide-and-conquer approach when it comes to array segments. We compute and store the sum of the elements of the whole array, i.e., the sum of the segment a[0 … n - 1]

We then split the array into two halves a[0 … (n - 1)/2] and a[(n + 1)/2 … n - 1] and compute the sum of each half and store them. Each of these two halves is again split into two smaller halves, and this process continues until all segments reach size 1.

These segments can be viewed as forming a binary tree. The root of this tree represents the segment
a[0 … n - 1], and each internal node has exactly two children. This is why the data structure is called a Segment Tree, even though in most implementations the tree is not constructed explicitly.

Below is a visual representation of a Segment Tree built over the array: a = [1, 3, -2, 8, -7]

Segment Tree Structure

The height of a Segment Tree is O(log n), because when moving from the root to the leaves, the size of the segment is reduced by approximately half at each level.

Construction of the Segment Tree

Before constructing a Segment Tree, we need to decide two important things:

A node is called a leaf node if its segment represents only a single element of the array. In that case, the value stored in the node is simply the corresponding array element.

To build the Segment Tree, we start from the leaf nodes and assign their values. Using the merge operation, we then compute the values of their parent nodes. This process continues until we reach the root node, which represents the entire array.

In practice, the construction is described recursively in the opposite direction, starting from the root node:

By starting this process from the root node, we are able to construct the complete Segment Tree.

The time complexity of building a Segment Tree is O(n), assuming the merge operation takes constant time, since each node is processed exactly once.

Sum Queries and Update Queries

Sum Queries

To compute the sum of a range [l, r], we traverse the Segment Tree and use the precomputed sums stored in the nodes.

Since only a limited number of nodes are visited at each level and the height of the tree is O(log n), the total time complexity is O(log n).

Update Queries

An update query modifies a single element in the array. We recursively traverse the tree to the leaf node representing that index and update its value.

While returning back up the tree, we recompute the values of all affected parent nodes using the merge operation.

Since only one node per level is updated, the time complexity of an update query is also O(log n).

Implementation

There are may ways to implement a Segment Tree. One common approach is to use an array-based representation, where the tree is stored in a flat array. This method is efficient in terms of both time and space. if want to know why we use array to represent segment tree please refer to the CP-Algorithms.

Array-Based Representation

In an array-based representation, the Segment Tree is stored in an array of size 4 * n to accommodate all nodes. The root node is at index 0, and for any node at index i:

This Array-Based Representation allows for efficient traversal and updates without the need for explicit pointers.

📌 Variable Notation

Build Function

The build function constructs the segment tree using a recursive divide-and-conquer strategy. It takes the current segment tree index idx, the array range [l, r], and the input array arr.

When l == r, the recursion reaches a leaf node, representing a single element of the array. At this point, the value arr[l] is directly stored in seg[idx].

Otherwise, the current range is split at mid into two subranges:[l, mid] and [mid + 1, r]. The function then recursively builds the left child at 2 * idx + 1 and the right child at 2 * idx + 2.

After both children are constructed, the current node aggregates their values. In this implementation, the aggregation operation is sum:seg[idx] = seg[2 * idx + 1] + seg[2 * idx + 2]. This process continues upward until the root node is built.

Range Sum Query Function

The rangeSumQuery function is used to compute the sum of elements within a given query range [ql, qr] using the segment tree. It operates on a node at index idx representing the segment [l, r].

To answer a query efficiently, the algorithm evaluates three possible overlap cases between the current segment and the query range.

This operation is read-only. The segment tree is not modified during a range sum query, as no updates are required. The result is obtained purely through traversal and aggregation.

Point Update Function

The pointUpdate operation updates the value at a specific array index and reflects this change in the segment tree. It takes five parameters: the target index, the new value val, the segment tree index idx, and the segment range [l, r].

The update process follows a binary-search–like traversal. At each recursive step, the current segment [l, r] is divided at mid to determine whether the target index lies in the left subtree [l, mid] or the right subtree [mid + 1, r].

This process continues recursively until l === r, which represents a leaf node. This node corresponds exactly to the update index, and its value is increased by val.

While returning from recursion, each ancestor node is recomputed to maintain correctness of the tree. For a sum segment tree, the parent value is updated as:
seg[idx] = seg[2 * idx + 1] + seg[2 * idx + 2]
This ensures that all affected ranges reflect the updated index.

Complete Segment Tree Code

Here is a sample code snippet for Segment Tree sum operations:

Summary

I have explained Segment Trees based on my own understanding and what I learned while studying them. I hope this guide is helpful to you. If you notice any mistakes or areas for improvement, please feel free to report them.

Practice Problem

You can practice Segment Tree problems on various competitive programming platforms. Here are a few recommended problems to get you started:

These problems will help you understand the implementation and application of Segment Trees in various scenarios. Happy coding!

Further Reading

For more in-depth information on Segment Trees, consider exploring the following resources:

These resources will provide you with a deeper understanding of Segment Trees and their applications in competitive programming and algorithm design.

Frequently Asked Questions