56.range Query 1-NOTES
56.range Query 1-NOTES
Segment Tree
Introduction
Suppose we have a problem in which two operations are performed:
Brute Force
Operation Time Complexity of Operation
1. Sum (array[0] to array[4]) O(N)
2. array[2] = 6 O(1)
3. array[3] = 9 O(1)
4. Sum (array[1] to array[4]) O(N)
1
Sum(array[2] to array[4]) = Sum(array[0] to array[4]) – Sum(array[0] to
array[1])
= ArrSum[4] – ArrSum[1]
Therefore, the time complexity of solving the query reduces to O(1) but for
update operations, in the worst case we have to traverse the entire
“ArrSum”. Hence, the time complexity of update operation increases to O(N).
2
In the Segment Tree, most of the solutions to queries are already present at
its nodes, for those queries whose solutions are not directly present like
Sum(array[5] to array[7]), we can calculate it by using the [5, 8] and [8] nodes.
Even for the worst-case scenario, that is, traversing through the entire tree,
the time complexity of the array will be equal to O(log(N)) which is equal to
the height of the tree.
Building a Segment tree requires O(N) time because there are N leaf nodes
in a segment tree and the remaining nodes are equal to N – 1. Therefore, the
total number of nodes = ( N ) + ( N – 1 ) = 2N – 1.
int main () {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
void buildTree(int* arr, int* tree, int start, int end, int treeNode){
// BASE CASE
if(start == end){
tree[treeNode] = arr[start];
}
3
// the answer to the recursive calls gets stored in
// the treeNode
tree[treeNOde] = tree[2*treeNode] + tree[2*treeNode + 1]
}
The size of the segment tree is twice the size of our initial array and the
indexing starts from 1 instead of 0.
Here there are two recursive calls happening for every treeNode and the
sum of their answers gets stored inside the treeNode.
First we find mid, then decide either to go left or right, that is, if the index
where we have to update the value is greater than mid then we go right, else
we go left. Even if the values at other nodes get affected by updating a single
node, we only travel the whole height of the tree in the worst case scenario,
hence, the time complexity for update operation is O(log(N)).
4
Example: For the update function at [8] or 9th index of the segment
tree, only the values at 4 nodes need to be changed.
void updateTree(int* arr, int* tree, int start, int end, int treeNode, int idx , int
value){
5
Query on the Segment Tree
● At A[0, 5] : the value of this node doesn’t give us the answer to our
query. Hence, we have to call recursion, now our query requires a sum
from 2 to 4th index so it is partially inside A[0, 2] and A[3, 5]. Therefore,
a recursion call is made to both the left and right subtree.
6
● At A[0, 2] : recursion call to A[0, 1] is not made because 0 and 1 are
completely outside the range of our query and the answer of A[2, 2]
which is completely inside the range of our query is returned using a
recursive call.
int query(int *tree, int start, int end, int treeNode, int left, int right){
// completely outside the given range
if(start > right || end < left){
return 0;
}
// completely inside the given range
if(start >= left && end <= right){
return tree[treeNode];
}
// Partially inside and partially outside
int mid = (start+end)/2;
int ans1 = query(tree, start, mid, 2*treeNode, left, right);
int ans2 = query(tree, mid+1, end, 2*treeNode+1, left, right);
7
● Total number of nodes = 20 + 21 + 22 + ……. + 2ceil (log(N))a
= 2*2ceil (log(N))
= 4N (approx)
8
Maximum Pair Sum
Problem Statement: Suppose we are given an array = [2, 3, 1, 5, 7, 6] and we
have to find a pair that gives us the maximum sum.
Explanation:
Segment Tree Approach: We can use the segment tree in the above
problem by calculating the maximum(max) and second maximum(smax) at
each node of the segment tree.
For the node that stores the answer of (0, 5), we have one left node and one
right node and we have the max and smax from both left and right nodes.
Now, the max of their parent node will be from the max of the left or max of
the right node.
9
If the right node’s max is chosen then smax of the parent node will be the
max of the left node or smax of the right node.
And vice versa.
The code is pretty similar to what we have done until now so try to do it on
your own.
Lazy Propagation
Earlier we used to update the value on a single index but we use lazy
propagation when we have to update in a range of indexes.
10
When there are many updates and updates are done on a range, we can
postpone some updates (avoid recursive calls in update) and do those
updates only when required.
We make a lazy tree with the same structure as our segment tree. A value of
0 at any node represents that there are no pending updates on that node. A
non-zero value represents the amount to be added to the node in the
segment tree before making any query to the node.
Algorithm:
updateRange(l, r)
● If the current segment tree node has any pending update, then first
add that pending update to the current node.
● If the current node’s range lies completely in the update query range:
○ Update current node
○ Postpone updates to children by setting lazy values for children
nodes.
● If the current node’s range overlaps with the update range, follow the
same approach as above.
○ Recur for left and right children.
○ Update current node using results of left and right calls.
11
12
13
Code :
#include<bits/stdc++.h>
using namespace std;
if(start == end){
tree[treeNode] = arr[start];
return;
}
int mid = (start+end)/2;
buildTree(arr,tree,start,mid,2*treeNode);
buildTree(arr,tree,mid+1,end,2*treeNode+1);
tree[treeNode] = min(tree[2*treeNode],tree[2*treeNode+1]);
}
if(lazy[currPos] !=0){
tree[currPos] += lazy[currPos];
if(low!=high){
lazy[2*currPos] += lazy[currPos];
lazy[2*currPos+1] += lazy[currPos];
}
lazy[currPos] = 0;
}
// No overlap
if(startR > high || endR < low){
return;
}
// Complete Overlap
14
if(startR<= low && high <= endR){
tree[currPos] += inc;
if(low!=high){
lazy[2*currPos] += inc;
lazy[2*currPos+1] += inc;
}
return;
}
// Partial Overlap
int main(){
int arr[] = {1,3,-2,4};
int* tree = new int[12]();
buildTree(arr,tree,0,3,1);
int* lazy = new int[12]();
updateSegmentTreeLazy(tree,lazy,0,3,0,3,1,3);
updateSegmentTreeLazy(tree,lazy,0,3,0,1,1,2);
15
Live Problems
Circular RMQ
Problem Statement:
You are given circular array a0, a1, ..., an - 1. There are two types of operations with
it:
Algorithm:
● In this problem the main issue is that the query is circular to get rid of it we
can break down the queries according to 2 cases.
○ if(L<=R) , then we process the query simply [L,R]
○ if(L>R), In this case we will break the query into two parts [L,N] + [0,R]
and solve them independently and after getting the output we simply
take the minimum of these two.
● Now using the above concept now we don’t have to worry that the query is
circular. Now we have to take care of the updation query for that we can
use lazy propagation.
16
● In, lazy propagation we simply check if the previous minimum is x and in the
updation query we are adding y to all the nodes then the minimum of that
node will also get incremented by y. (lazy[pos]+=y).
● Now if we talk about the segment tree node. It will contain the minimum of a
range denoted by that node. Therefore in the push down method if there
are some pending lazy we will simply increment or update the value of the
segment tree by that lazy value St[pos]+=lazy[pos]. Because all the element
in that range is increase by lazy[pos] therefore the minimum of that range
will also increase by lazy[pos]
Code:
#include<bits/stdc++.h>
#define int long long
#define M (beg+end)/2
using namespace std;
17
}
lazy[pos] = 0;
}
}
build(beg, M, 2*pos+1);
build(M+1, end, 2*pos+2);
st[pos] = min(st[2*pos+1], st[2*pos+2]);
}
void update(int beg, int end, int l, int r, int v, int pos) {
pushdown(pos, beg, end);
if(l <= beg and r >= end) {
lazy[pos] += v;
pushdown(pos, beg, end);
return;
} else if(l>end or r<beg) {
return;
}
update(beg, M, l, r, v, 2*pos+1);
update(M+1, end, l, r, v, 2*pos+2);
st[pos] = min(st[2*pos+1], st[2*pos+2]);
}
18
}
return min(query(beg, M, l, r, 2*pos+1), query(M+1, end, l, r, 2*pos+2));
}
signed main() {
freopen("input.txt", "r", stdin);
freopen("out.txt", "w", stdout);
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin>>n;
19
} else {
cout<< min(query(0, n-1, l, n-1, 0), query(0, n-1, 0, r,
0)) <<"\n";
}
}
}
return 0;
}
K - Query Online
Problem Statement:
Given a sequence of n numbers a1, a2, ..., an and a number of k-queries. A k-query is
a triple (i, j, k) (1 ≤ i ≤ j ≤ n). For each k-query (i, j, k), you have to return the number
of elements greater than k in the subsequence ai, ai+1, ..., aj.
Algorithm:
● Imagine we have an array b1, b2, ..., bn which, and bi = 1 if and only
if ai > k, then we can easily answer the query (i, j, k) in O(log(n)) using a
simple segment tree (answer is bi + bi + 1 + ... + bj ).
● First of all, read all the queries and save them somewhere, then sort them in
increasing order of k and also the array a in increasing order (compute the
permutation p1, p2, ..., pn where ap1 ≤ ap2 ≤ ... ≤ apn)
● At first we'll set all array b to 1 and we will set all of them to 0 one by one.
20
● Consider after sorting the queries in increasing order of their k, we have a
permutation w1, w2, ..., wq (of 1, 2, ..., q) where kw1 ≤ kw2 ≤ kw2 ≤ ... ≤ kwq (we
keep the answer to the i - th query in ansi .
Code:
#include<bits/stdc++.h>
#define M (beg + end)/2
using namespace std;
vector<int> st[4*N];
int arr[N];
build(beg, M, 2*pos+1);
build(M+1, end, 2*pos+2);
int query(int beg, int end, int ql, int qr, int k, int pos) {
if(ql <= beg && qr >= end) {
auto it = upper_bound(st[pos].begin(), st[pos].end(), k);
int numLesser = it - st[pos].begin();
return (end - beg + 1 - numLesser);
} else if(ql > end or qr < beg) {
return 0;
}
return query(beg, M, ql, qr, k, 2*pos+1) + query(M+1, end, ql, qr, k, 2*pos+2);
}
21
int main() {
freopen("input.txt", "r", stdin);
freopen("out.txt", "w", stdout);
int n;
cin>>n;
int q;
int ans = 0;
cin>>q;
while(q--) {
int a, b, c;
cin>>a>>b>>c;
a ^= ans;
b ^= ans;
c ^= ans;
if(a < 1) a=1;
if(b > n) b=n;
if(a > b) {
ans = 0;
cout<<ans<<"\n";
continue;
}
a--;
b--;
return 0;
}
22