You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/data-structures/segment-tree.md
+22-73Lines changed: 22 additions & 73 deletions
Original file line number
Diff line number
Diff line change
@@ -5,13 +5,12 @@ tags:
5
5
- Segment Tree
6
6
---
7
7
8
-
# Segment Tree
9
8
Segment Tree is a data structure that enables us to answer queries like minimum, maximum, sum etc. for any $[l,r]$ interval in $\mathcal{O}(\log N)$ time complexity and update these intervals.
10
9
11
10
Segment Tree is more useful than [Fenwick Tree](fenwick-tree.md) and [Sparse Table](sparse-table.md) structures because it allows updates on elements and provides the possibility to answer queries like minimum, maximum etc. for any $[l,r]$ interval. Also, the memory complexity of Segment Tree is $\mathcal{O}(N)$ while the memory complexity of the Sparse Table structure is $\mathcal{O}(N \log N)$.
12
11
13
12
## Structure and Construction
14
-
Segment Tree has a "Complete Binary Tree" structure. The leaf nodes of the Segment Tree store the elements of the array, and each node's value is the sum of its children's values. Thus, the answers of certain intervals are stored in each node, and the answer of the whole array is stored in the root node. For example, for a Segment Tree structure built for the sum query, the value of each node is equal to the sum of its children's values.
13
+
Segment Tree has a "Complete Binary Tree" structure. The leaf nodes of the Segment Tree store the elements of the array, and each internal node's value is calculated with a function that takes its children's values as inputs. Thus, the answers of certain intervals are stored in each node, and the answer of the whole array is stored in the root node. For example, for a Segment Tree structure built for the sum query, the value of each node is equal to the sum of its children's values.
15
14
16
15
<figuremarkdown="span">
17
16
![segment tree structure to query sum on array a = [41,67,6,30,85,43,39]](img/segtree.png){ width="100%" }
@@ -33,9 +32,9 @@ void build(int ind, int l, int r) {
33
32
}
34
33
```
35
34
36
-
## Aralık Sorgu ve Eleman Güncelleme
35
+
## Query and Update Algorithms
37
36
38
-
### Sorgu Algoritması
37
+
### Query Algorithm
39
38
40
39
For any $[l,r]$ interval, the query algorithm works as follows:
41
40
- Divide the $[l,r]$ interval into the widest intervals that are stored in the tree.
@@ -66,10 +65,9 @@ int query(int ind, int l, int r, int lw, int rw) {
66
65
}
67
66
```
68
67
69
-
### Eleman Güncelleme Algoritması
68
+
### Update Algorithm
70
69
71
-
The algorithm used to update the value of the $x$ indexed element in the array works as follows:
72
-
- The value of the leaf node containing the $x$ indexed element is updated.
70
+
Update the value of every node that contains $x$ indexed element.
73
71
74
72
It is sufficient to update the values of at most $\log(N)$ nodes from the leaf node containing the $x$ indexed element to the root node. Therefore, the time complexity of updating the value of any element is $\mathcal{O}(\log N)$.
75
73
@@ -101,49 +99,28 @@ void update(int ind, int l, int r, int x, int val) {
101
99
A sample problem related to the Segment Tree data structure can be found [here](https://codeforces.com/gym/100739/problem/A){target="_blank"}.
102
100
103
101
## Segment Tree with Lazy Propagation
104
-
Previously, update function was called to update only a single value in array. Please
105
-
note that a single value update in array may cause changes in multiple nodes in Segment Tree as
106
-
there may be many segment tree nodes that have this changed single element in it’s range.
102
+
Previously, update function was called to update only a single value in array. Please note that a single value update in array may cause changes in multiple nodes in Segment Tree as there may be many segment tree nodes that have this changed single element in it’s range.
107
103
108
104
### Lazy Propogation Algorithm
109
105
We need a structure that can perform following operations on an array $[1,N]$.
110
106
- Add inc to all elements in the given range $[l, r]$.
111
107
- Return the sum of all elements in the given range $[l, r]$.
112
108
113
-
Notice that if update was for single element, we could use the segment tree we have learned before.
114
-
Trivial structure comes to mind is to use an array and do the operations by traversing and increasing
115
-
the elements one by one. Both operations would take $\mathcal{O}(L)$ time complexity in this structure where
116
-
$L$ is the number of elements in the given range.
109
+
Notice that if update was for single element, we could use the segment tree we have learned before. Trivial structure comes to mind is to use an array and do the operations by traversing and increasing the elements one by one. Both operations would take $\mathcal{O}(L)$ time complexity in this structure where $L$ is the number of elements in the given range.
117
110
118
-
Let’s use segment tree’s we have learned. Second operation is easy, We can do it in $\mathcal{O}(\log N)$. What
119
-
about the first operation. Since we can do only single element update in the regular segment tree,
120
-
we have to update all elements in the given range one by one. Thus we have to perform update
121
-
operation $L$ times. This works in $\mathcal{O}(L \times \log N)$ for each range update. This looks bad, even worse than
122
-
just using an array in a lot of cases.
111
+
Let’s use segment tree’s we have learned. Second operation is easy, We can do it in $\mathcal{O}(\log N)$. What about the first operation. Since we can do only single element update in the regular segment tree, we have to update all elements in the given range one by one. Thus we have to perform update operation $L$ times. This works in $\mathcal{O}(L \times \log N)$ for each range update. This looks bad, even worse than just using an array in a lot of cases.
123
112
124
-
So we need a better structure. People developed a trick called lazy propagation to perform range
125
-
updates on a structure that can perform single update (This trick can be used in segment trees,
126
-
treaps, k-d trees ...).
113
+
So we need a better structure. People developed a trick called lazy propagation to perform range updates on a structure that can perform single update (This trick can be used in segment trees, treaps, k-d trees ...).
127
114
128
-
Trick is to be lazy i.e, do work only when needed. Do the updates only when you have to. Using
129
-
Lazy Propagation we can do range updates in $\mathcal{O}(\log N)$ on standart segment tree. This is definitely
130
-
fast enough.
115
+
Trick is to be lazy i.e, do work only when needed. Do the updates only when you have to. Using Lazy Propagation we can do range updates in $\mathcal{O}(\log N)$ on standart segment tree. This is definitely fast enough.
131
116
132
117
### Updates Using Lazy Propogation
133
-
Let’s be <i>lazy</i> as told, when we need to update an interval, we will update a node and mark its
134
-
children that it needs to be updated and update them when needed. For this we need an array
135
-
$lazy[]$ of the same size as that of segment tree. Initially all the elements of the $lazy[]$ array will be $0$
136
-
representing that there is no pending update. If there is non-zero element $lazy[k]$ then this element
137
-
needs to update node k in the segment tree before making any query operation, then $lazy[2\cdot k]$ and
138
-
$lazy[2 \cdot k + 1]$ must be also updated correspondingly.
118
+
Let’s be <i>lazy</i> as told, when we need to update an interval, we will update a node and mark its children that it needs to be updated and update them when needed. For this we need an array $lazy[]$ of the same size as that of segment tree. Initially all the elements of the $lazy[]$ array will be $0$ representing that there is no pending update. If there is non-zero element $lazy[k]$ then this element needs to update node k in the segment tree before making any query operation, then $lazy[2\cdot k]$ and $lazy[2 \cdot k + 1]$ must be also updated correspondingly.
139
119
140
120
To update an interval we will keep 3 things in mind.
141
-
- If current segment tree node has any pending update, then first add that pending update to
142
-
current node and push the update to it’s children.
143
-
- If the interval represented by current node lies completely in the interval to update, then update
144
-
the current node and update the $lazy[]$ array for children nodes.
145
-
- If the interval represented by current node overlaps with the interval to update, then update
146
-
the nodes as the earlier update function.
121
+
- If current segment tree node has any pending update, then first add that pending update to current node and push the update to it’s children.
122
+
- If the interval represented by current node lies completely in the interval to update, then update the current node and update the $lazy[]$ array for children nodes.
123
+
- If the interval represented by current node overlaps with the interval to update, then update the nodes as the earlier update function.
147
124
148
125
```c++
149
126
void update(int node, int start, int end, int l, int r, int val) {
@@ -184,18 +161,10 @@ void update(int node, int start, int end, int l, int r, int val) {
184
161
}
185
162
```
186
163
187
-
This is the update function for given problem. Notice that when we arrive a node, all the updates that
188
-
we postponed that would effect this node will be performed since we are pushing them downwards
189
-
as we go to this node. Thus this node will keep the exact values when the range updates are done
190
-
without lazy. So it’s seems like it is working. How about queries?
164
+
This is the update function for given problem. Notice that when we arrive a node, all the updates that we postponed that would effect this node will be performed since we are pushing them downwards as we go to this node. Thus this node will keep the exact values when the range updates are done without lazy. So it’s seems like it is working. How about queries?
191
165
192
166
### Queries Using Lazy Propogation
193
-
Since we have changed the update function to postpone the update operation, we will have to change
194
-
the query function also. The only change we need to make is to check if there is any pending update
195
-
operation on that node. If there is a pending update operation, first update the node and then work
196
-
same as the earlier query function. As we told in the last subsection, all the postponed updates that
197
-
would effect this node will be performed before we reach this node. So sum value we look for will be
198
-
there, correctly!
167
+
Since we have changed the update function to postpone the update operation, we will have to change the query function as well. The only change we need to make is to check if there is any pending update operation on that node. If there is a pending update, first update the node and then proceed the same way as the earlier query function. As mentioned in the previous subsection, all the postponed updates that would affect this node will be performed before we reach it. Therefore, the sum value we look for will be correct.
199
168
200
169
```c++
201
170
intquery(int node, int start, int end, int l, int r) {
@@ -229,25 +198,15 @@ int query(int node, int start, int end, int l, int r) {
229
198
return (p1 + p2);
230
199
}
231
200
```
232
-
233
-
Notice that only difference with regular query function is pushing the lazy values downwards as we
234
-
travel. This is a widely used trick. You can use it for dozens of different problems. But not all range
235
-
problems. You may have noticed that we used some properties of adding operation here. We used
236
-
the fact that increasing updates has associative property. We merged more than one updates in lazy
237
-
array, we didn’t care about the order of this updates. This assumption must be made to use lazy
238
-
propagation. Other properties that needed left as an exercise to the readers.
201
+
Notice that the only difference with the regular query function is pushing the lazy values downwards as we traverse. This is a widely used trick applicable to various problems, though not all range problems. You may notice that we leveraged properties of addition here. The associative property of addition allows merging multiple updates in the lazy array without considering their order. This assumption is crucial for lazy propagation. Other necessary properties are left as an exercise to the reader.
239
202
240
203
## Binary Search on Segment Tree
241
-
Assume we have an array A that contains elements between 1 and $M$. We have to perform 2 kinds
242
-
of operations.
204
+
Assume we have an array A that contains elements between 1 and $M$. We have to perform 2 kinds of operations.
243
205
- Change the value of the element in given index i by x.
244
206
- Return the value of the kth element on the array when sorted.
245
207
246
208
### How to Solve It Naively
247
-
Let’s construct a frequency array, $F[i]$ will keep how many times number i occurs in our original
248
-
array. So we want to find smallest $i$ such that $\sum_{j=1}^{i} F[i] \geq k$.
249
-
. Then the number $i$ will be our answer
250
-
for the query. And for updates we just have to change $F$ array accordingly.
209
+
Let’s construct a frequency array, $F[i]$ will keep how many times number i occurs in our original array. So we want to find smallest $i$ such that $\sum_{j=1}^{i} F[i] \geq k$. Then the number $i$ will be our answer for the query. And for updates we just have to change $F$ array accordingly.
This is of course, slow. Let’s use segment tree’s to improve it. First we will construct a segment tree
280
-
on $F$ array. Segment tree will perform single element updates and range sum queries. We will use
281
-
binary search to find corresponding $i$ for $k^{th}$ element queries.
238
+
This is of course, slow. Let’s use segment tree’s to improve it. First we will construct a segment tree on $F$ array. Segment tree will perform single element updates and range sum queries. We will use binary search to find corresponding $i$ for $k^{th}$ element queries.
282
239
283
240
<figuremarkdown = "span">
284
241
{ width="100%" }
@@ -304,18 +261,10 @@ int query(int k) {
304
261
}
305
262
```
306
263
307
-
If you look at the code above you can notice that each update takes $\mathcal{O}(\log M)$ time and each query
308
-
takes $\mathcal{O}(\log^{2} M)$ time, but we can do better.
264
+
If you look at the code above you can notice that each update takes $\mathcal{O}(\log M)$ time and each query takes $\mathcal{O}(\log^{2} M)$ time, but we can do better.
309
265
310
266
### How To Speed Up?
311
-
If you look at the segment tree solution on preceding subsection you can see that queries are performed
312
-
in $\mathcal{O}(\log^{2} M)$ time. We can make is faster, actually we can reduce the time complexity to $\mathcal{O}(\log M)$
313
-
which is same with the time complexity for updates. We will do the binary search when we are
314
-
traversing the segment tree. We first will start from the root and look at its left child’s sum value, if
315
-
this value is greater than k, this means our answer is somewhere in the left child’s subtree. Otherwise
316
-
it is somewhere in the right child’s subtree. We will follow a path using this rule until we reach a
317
-
leaf, then this will be our answer. Since we just traversed $\mathcal{O}(\log M)$ nodes (one node at each level),
318
-
time complexity will be $\mathcal{O}(\log M)$. Look at the code below for better understanding.
267
+
If you look at the segment tree solution on preceding subsection you can see that queries are performed in $\mathcal{O}(\log^{2} M)$ time. We can make is faster, actually we can reduce the time complexity to $\mathcal{O}(\log M)$ which is same with the time complexity for updates. We will do the binary search when we are traversing the segment tree. We first will start from the root and look at its left child’s sum value, if this value is greater than k, this means our answer is somewhere in the left child’s subtree. Otherwise it is somewhere in the right child’s subtree. We will follow a path using this rule until we reach a leaf, then this will be our answer. Since we just traversed $\mathcal{O}(\log M)$ nodes (one node at each level), time complexity will be $\mathcal{O}(\log M)$. Look at the code below for better understanding.
319
268
320
269
<figure markdown = "span">
321
270
{ width="100%" }
0 commit comments