Motivational Problems:
http://www.spoj.pl/problems/BRCKTS/
http://www.spoj.com/problems/GSS3/
http://www.spoj.com/problems/HORRIBLE
http://www.spoj.pl/problems/IOPC1207/
https://www.spoj.com/problems/GSS2/
http://www.spoj.com/problems/SEGSQRSS/
http://www.spoj.com/problems/ORDERSET/
http://www.spoj.com/problems/HELPR2D2/
http://www.spoj.com/problems/TEMPLEQ
Segment trees (shortened as segtrees), as some of you might have heard of, is a cool data structure, primarily used for range queries. It is a height balanced binary tree with a static structure. The nodes of segment tree correspond to various intervals, and can be augmented with appropriate information pertaining to those intervals. It is somewhat less powerful than balanced binary trees because of its static structure, but due to the recursive nature of operations on the segtree, it is incredibly easy to think about and code.
Structure
In a segtree, the basic structure (an array in almost all cases) on which you want to answer queries are stored at leaves of the tree, in the sequential order. All internal nodes is formed by "merging" its left and right children. The overall tree is a complete binary tree of height n.
The diagram shows how the binary tree is built up and how the nodes are indexed. Given a node with label i, its left and right children are 2i and 2i+1 respectively, and nodes i*2k to (i+1)*2k -1 are its kth level descendants. However, segtrees are most effective when you think of them as just a recursive structure.
As an example, consider a segment tree with n=3. As in the above figure, this would mean that the segment tree has 15 nodes. Such a segment tree could be used to store ranges corresponding to an array of size 8 (indices 0 through 7). The leaf nodes (8 to 15) all correspond to intervals containing one element only : That is, node 8 would correspond to the interval [0,0] in the array and node 9 would be [1,1] and so on. As we go up the segtree, the interval corresponding to each node in the segtree is found by merging the intervals of its two children. That way, node 4 will correspond to interval [0,1] and node 3 to interval [4,7] and so on.
Now assume that you need to make a query on some arbitrary interval in the array. The most straightforward way would be to look at the lowermost level in the segment tree. But that would require as many operations as there are elements in the interval, and is hence costly. For example, If one desires to query the interval [2,7], this would mean having to look at the nodes 10, 11, 12, 13, 14 and 15 in the segtree. But we can be smart and choose only nodes 3 and 5 : the former takes care of the interval [4,7] while the latter takes care of [2,3]. When we have longer intervals and thus deeper segtrees, the advantage gained by choosing conjoined intervals is huge. The basic idea behind segment trees is to recursively choose the best intervals for updation and querying such that the total number of operations needed is reduced.
The exact implementation is detailed in the rest of the article. In this blog, I go on to show that most of the times, a segtree can be described by these fundamental operations: merge, split and update_single_subtree. Add to it binary_search, and almost every variant of segtree that I have come across can be broken down in terms of these operations. At the end of this blog, I will give a stl-like packaged version of segtree.
Basic Idea:
A segtree can be seen of as doing two things at the same time: a) The internal nodes summarize the information stored at descendant leaves, so that the information about all the leaves can be extracted quickly. The merge operation does this task of summarizing the information stored at two nodes into a single node. b) The internal nodes store the operations that have to be applied to its descendants. This gets propagated down in a lazy manner. The update_single_subtree puts information about how to update the leaves into a single node, and split operation propagates this down the tree in a lazy manner. For example, if you have to add a constant C to all descendant leaves, the Update_single_subtree operation will store the value of C at the node, and split operation will pass down the value of C to both its children.
Merge Operation:
Example 1
Lets try to solve the following problem using segtrees:
Given an array A[1 ... m], you want to perform the following operations:
a) report minimum element in a range A[i ... j]
b) set A[i] = x.
Choose a value of n = depth of your tree = ceil (log m), so that all the array elements can fit at the lowest level of your tree. The merge function for this problem does exactly what you think ! Yes, it will just take the two values and return the minimum. So the first operation that is required will be taken care of by merge itself. The second operation requires us to modify one of the leaves of our tree. There are two possible approaches: a) Update the leaf, and notify all of its parents in the tree, to update the information they store.
b) Implement the update_single_subtree function which will update the value of node. You will not not need to implement a split function because information is not propagated downwards.
Example 2
problem: http://www.spoj.com/problems/GSS3/
The data stored at node and merge function are given below. Rest of the code remains same as Example 1 above. Just to make things clearer, I have also given how to create a single leaf from a value.
Exercise:
Using only the above concept of merge function, try solving the following problem:
http://www.spoj.pl/problems/BRCKTS/
Split Operation:
Example 3:
Let us add another operation to example 1:
c) add a constant C to all elements in the range A[i..j]
Now you will have to add another parameter to your node structure, which is the value you have to add to all the descendants of that node.
I show the modified merge and the split functions. Also, the update_single_node functions for operation c) is shown. An observant coder might notice that the update function for operation b) will need to be changed slightly, as shown below.
The incredible thing that I feel about segtrees is that it is completely defined by the structure of the node, and the merge and split operations. Therefore, all the remaining code does not change.
Example 4:
problem http://www.spoj.com/problems/HORRIBLE/
The relevant functions are given below:
Exercise:
Try solving
http://www.spoj.pl/problems/IOPC1207/
(Hint: you will need to maintain 3 separate trees, one each for X, Y and Z axis).
http://www.spoj.com/problems/SEGSQRSS/
http://www.spoj.com/problems/DQUERY/
(Hint: you will need to store all the queries and process them offline)
https://www.spoj.com/problems/GSS2/
(Hint: you will need to store all the queries and process them offline)
Binary Search:
Often, you are required to locate the first leaf which satisfies some property. In order to do this you have to search in the tree, where you traverse down a path in the tree by deciding at each point whether to go to the left left subtree, or to the right subtree. For example, consider the following problem: http://www.spoj.com/problems/ORDERSET/
Here, you have to first apply co-ordinate compression to map the numbers from range [1 .. 109] to [1 .. Q]. You can use stl maps for doing that, so assuming that that is done, you would again need a simple segtree as shown. Note that INSERT and DELETE operation can be defined by just implementing the update_single_subtree function separately for each case. Also, COUNT(i) is nothing but query(0,i). However, to implement k-TH, you will need to do a binary search as shown below:
http://www.spoj.pl/problems/BRCKTS/
http://www.spoj.com/problems/GSS3/
http://www.spoj.com/problems/HORRIBLE
http://www.spoj.pl/problems/IOPC1207/
https://www.spoj.com/problems/GSS2/
http://www.spoj.com/problems/SEGSQRSS/
http://www.spoj.com/problems/ORDERSET/
http://www.spoj.com/problems/HELPR2D2/
http://www.spoj.com/problems/TEMPLEQ
Segment trees (shortened as segtrees), as some of you might have heard of, is a cool data structure, primarily used for range queries. It is a height balanced binary tree with a static structure. The nodes of segment tree correspond to various intervals, and can be augmented with appropriate information pertaining to those intervals. It is somewhat less powerful than balanced binary trees because of its static structure, but due to the recursive nature of operations on the segtree, it is incredibly easy to think about and code.
Structure
In a segtree, the basic structure (an array in almost all cases) on which you want to answer queries are stored at leaves of the tree, in the sequential order. All internal nodes is formed by "merging" its left and right children. The overall tree is a complete binary tree of height n.
![]() |
| A segtree on 2n elements. The children of node labelled i are (2*i) and (2*i+1). |
The diagram shows how the binary tree is built up and how the nodes are indexed. Given a node with label i, its left and right children are 2i and 2i+1 respectively, and nodes i*2k to (i+1)*2k -1 are its kth level descendants. However, segtrees are most effective when you think of them as just a recursive structure.
As an example, consider a segment tree with n=3. As in the above figure, this would mean that the segment tree has 15 nodes. Such a segment tree could be used to store ranges corresponding to an array of size 8 (indices 0 through 7). The leaf nodes (8 to 15) all correspond to intervals containing one element only : That is, node 8 would correspond to the interval [0,0] in the array and node 9 would be [1,1] and so on. As we go up the segtree, the interval corresponding to each node in the segtree is found by merging the intervals of its two children. That way, node 4 will correspond to interval [0,1] and node 3 to interval [4,7] and so on.
Now assume that you need to make a query on some arbitrary interval in the array. The most straightforward way would be to look at the lowermost level in the segment tree. But that would require as many operations as there are elements in the interval, and is hence costly. For example, If one desires to query the interval [2,7], this would mean having to look at the nodes 10, 11, 12, 13, 14 and 15 in the segtree. But we can be smart and choose only nodes 3 and 5 : the former takes care of the interval [4,7] while the latter takes care of [2,3]. When we have longer intervals and thus deeper segtrees, the advantage gained by choosing conjoined intervals is huge. The basic idea behind segment trees is to recursively choose the best intervals for updation and querying such that the total number of operations needed is reduced.
The exact implementation is detailed in the rest of the article. In this blog, I go on to show that most of the times, a segtree can be described by these fundamental operations: merge, split and update_single_subtree. Add to it binary_search, and almost every variant of segtree that I have come across can be broken down in terms of these operations. At the end of this blog, I will give a stl-like packaged version of segtree.
Basic Idea:
A segtree can be seen of as doing two things at the same time: a) The internal nodes summarize the information stored at descendant leaves, so that the information about all the leaves can be extracted quickly. The merge operation does this task of summarizing the information stored at two nodes into a single node. b) The internal nodes store the operations that have to be applied to its descendants. This gets propagated down in a lazy manner. The update_single_subtree puts information about how to update the leaves into a single node, and split operation propagates this down the tree in a lazy manner. For example, if you have to add a constant C to all descendant leaves, the Update_single_subtree operation will store the value of C at the node, and split operation will pass down the value of C to both its children.
Merge Operation:
Example 1
Lets try to solve the following problem using segtrees:
Given an array A[1 ... m], you want to perform the following operations:
a) report minimum element in a range A[i ... j]
b) set A[i] = x.
Choose a value of n = depth of your tree = ceil (log m), so that all the array elements can fit at the lowest level of your tree. The merge function for this problem does exactly what you think ! Yes, it will just take the two values and return the minimum. So the first operation that is required will be taken care of by merge itself. The second operation requires us to modify one of the leaves of our tree. There are two possible approaches: a) Update the leaf, and notify all of its parents in the tree, to update the information they store.
b) Implement the update_single_subtree function which will update the value of node. You will not not need to implement a split function because information is not propagated downwards.
Example 2
problem: http://www.spoj.com/problems/GSS3/
The data stored at node and merge function are given below. Rest of the code remains same as Example 1 above. Just to make things clearer, I have also given how to create a single leaf from a value.
Exercise:
Using only the above concept of merge function, try solving the following problem:
http://www.spoj.pl/problems/BRCKTS/
Split Operation:
Example 3:
Let us add another operation to example 1:
c) add a constant C to all elements in the range A[i..j]
Now you will have to add another parameter to your node structure, which is the value you have to add to all the descendants of that node.
I show the modified merge and the split functions. Also, the update_single_node functions for operation c) is shown. An observant coder might notice that the update function for operation b) will need to be changed slightly, as shown below.
The incredible thing that I feel about segtrees is that it is completely defined by the structure of the node, and the merge and split operations. Therefore, all the remaining code does not change.
Example 4:
problem http://www.spoj.com/problems/HORRIBLE/
The relevant functions are given below:
Exercise:
Try solving
http://www.spoj.pl/problems/IOPC1207/
(Hint: you will need to maintain 3 separate trees, one each for X, Y and Z axis).
http://www.spoj.com/problems/SEGSQRSS/
http://www.spoj.com/problems/DQUERY/
(Hint: you will need to store all the queries and process them offline)
https://www.spoj.com/problems/GSS2/
(Hint: you will need to store all the queries and process them offline)
Binary Search:
Often, you are required to locate the first leaf which satisfies some property. In order to do this you have to search in the tree, where you traverse down a path in the tree by deciding at each point whether to go to the left left subtree, or to the right subtree. For example, consider the following problem: http://www.spoj.com/problems/ORDERSET/
Here, you have to first apply co-ordinate compression to map the numbers from range [1 .. 109] to [1 .. Q]. You can use stl maps for doing that, so assuming that that is done, you would again need a simple segtree as shown. Note that INSERT and DELETE operation can be defined by just implementing the update_single_subtree function separately for each case. Also, COUNT(i) is nothing but query(0,i). However, to implement k-TH, you will need to do a binary search as shown below:
Exercise:
solve http://www.spoj.com/problems/HELPR2D2/ by implementing merge, binary search and update_single_subtree.
Finally to conclude this blog, here is link to stl-type packaged version of segment trees as promised earlier. Example of using the segtree class by solving spoj problems:
http://www.spoj.pl/problems/BRCKTS/, sample code here
http://www.spoj.com/problems/HORRIBLE, sample code here
http://www.spoj.com/problems/ORDERSET/, sample code here
Acknowledgments:
Raziman TV for teaching me segment trees.
Meeting Mata Amritanandamayi at Amritapuri inspired me to do something to improve level of programming in India.

Awesome work!
ReplyDeleteGreat article! Looking forward even more articles from you. :)
ReplyDeleteVery good work !!!
ReplyDeleteSuper work. Looks like this place is going to be the go-to place for a guy aspiring to become a serious contender in programming contests.
ReplyDeleteThanks a lot!
nice one....Thanks for this...It will be good if we have a tutorial like this for binary indexed trees also....Thanks..:)
ReplyDeleteAbsolutely true @! :)
Deletegrt work..
ReplyDeletecan anyone help me to solve this problem http://www.spoj.com/problems/CTRICK/ using segment tree.
I have an solution in O(n^2) which gives TLE.
The function mergeup in example 1 will run forever because the value postn should be updated as postn >>= 1 in the while block, but it's not.
ReplyDeleteThanks for pointing. Corrected.
DeleteCan anyone help me out with http://www.spoj.pl/problems/IOPC1207/
ReplyDeleteHow exactly will 3 segment trees help? An update on one of the coordinates should also trigger an update on the other 2. How to manage all this? Will appreciate some help....
The three ranges(x,y,z) can be handled independently.
DeleteThanks for the reply... In one of the booklets, I had seen the following formula: r1r2r3 + r1g2g3 + g1r2g3 + g1g2r3. What is its derivation?
Deletecan u please explain in solution GSS3 that
ReplyDeletewhat represent the bestprefix and bestsuffix ???
As you already understand, a node of the segtree corresponds to a subarray of the original array. Now, bestSuffix of a node corresponds to suffix of that interval whose sum is largest. Therefore, bestSuffix stores the sum of the 'best' suffix. Similarly, bestPrefix of a node corresponds to prefix of that interval whose sum is largest.
Deletei am struggling with your brckts spoj code,,,where i dont understand what min is representing in each node.
ReplyDelete