0% found this document useful (0 votes)
14 views

56.range Query 1-NOTES

Uploaded by

SUBHASIS SAMANTA
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views

56.range Query 1-NOTES

Uploaded by

SUBHASIS SAMANTA
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 22

Range Queries I

Segment Tree
Introduction
Suppose we have a problem in which two operations are performed:

1. Range Query (finding sum of elements in a range).


2. Update Array element(s)

For example, in array = [1, 3, 5, 7, 6, 8] we have to perform the following


operations:

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)

Now, if we have K number of queries and L number of update operations


then the overall time complexity of the solution becomes O(K*N) + O(L*1).
This increases with respect to K. Therefore, this approach is only effective
when K is small.

Another approach to solve this is by maintaining another array in which the


ith element will store the sum from 0th to the ith element in our original array.

For example, ArrSum[2] = Sum(array[0] to array[2]).


Array = [1, 3, 5, 7, 6, 8]
ArrSum = [1, 4, 9, 16, 22, 30]

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).

Segment Tree helps us to perform both Query and Update Operations in


O(log(N)) time.

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 the Segment 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};

// the size of 2*size of arr


int *tree = new int[18] ;

buildTree(arr, tree, 0, 8, 1);


}

void buildTree(int* arr, int* tree, int start, int end, int treeNode){

// BASE CASE
if(start == end){
tree[treeNode] = arr[start];
}

int mid =(start+end)/2;


// two recursive calls to build the left half and
// right half respectively
buildTree(arr, tree, start, mid, 2*treeNode);
buildTree(arr, tree, mid+1, end, 2*treeNode+1);

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.

Updating the Segment Tree

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){

int mid = (start+end)/2;


if(idx > mid){
updateTree(arr, tree, mid+1, end, 2*treeNode+1, idx, value);
} else {
updateTree(arr, tree, start, mid, 2*treeNode, idx, value);
}
tree[treeNode] = tree[2*treeNode] + tree[2*treeNode+1];
}

5
Query on the Segment Tree

Example : A = [ 1, 2, 3, 4, 5, 6 ] Query: Sum(2 to 4)

● 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.

● At A[3, 5] : recursion call to A[5, 5] is not made because 5 lies


completely outside the range of our query while the answer to A[3, 4]
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);

return ans1 + ans2;


}

Size of the Segment Tree


Earlier we made the segment tree to be of size 2*N, because we were storing
2*N nodes. This doesn’t always work because like in the figure shown below
A[3, 3] and A[4, 4] are supposed to be stored at the 11th and 12th index rather
than the 9th and 10th index.

7
● Total number of nodes = 20 + 21 + 22 + ……. + 2ceil (log(N))a

= (1)(2ceil (log(N)) +1 -1) /(2-1)

= 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 tree after the build will look like this:

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;

void buildTree(int* arr,int* tree,int start,int end,int treeNode){

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]);
}

void updateSegmentTreeLazy(int* tree,int* lazy,int low,int high,int startR,int


endR,int currPos,int inc){

if(low > high){


return;
}

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 mid = (low+high)/2;


updateSegmentTreeLazy(tree,lazy,low,mid,startR,endR,2*currPos,inc);
updateSegmentTreeLazy(tree,lazy,mid+1,high,startR,endR,2*currPos+1,inc);
tree[currPos] = min(tree[2*currPos],tree[2*currPos+1]);
}

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);

cout<< "Segment Tree" <<endl;


for(int i=1;i<12;i++){
cout<<tree[i]<< endl;
}

cout<< "Lazy Tree" <<endl;


for(int i=1;i<12;i++){
cout<<lazy[i]<< endl;
}
return 0;
}

15
Live Problems

Circular RMQ

Problem Statement:

You are given circular array a0, a1, ..., an - 1. There are two types of operations with
it:

● inc(lf, rg, v) — this operation increases each element on the segment


[lf, rg] (inclusively) by v;
● rmq(lf, rg) — this operation returns the minimal value on the segment
[lf, rg] (inclusively).

Assume segments to be circular, so if n = 5 and lf = 3, rg = 1, it means the index


sequence: 3, 4, 0, 1.

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;

const int N = (int)(2e5+5);

const int MAX = (int)(1e13+1);


int a[N];
int st[4*N];
int lazy[4*N];

void pushdown(int pos, int beg, int end) {


if(lazy[pos] != 0) {
st[pos] += lazy[pos];
if(beg != end) {
lazy[2*pos+1] += lazy[pos];
lazy[2*pos+2] += lazy[pos];

17
}
lazy[pos] = 0;
}
}

void build(int beg, int end, int pos) {


if(beg == end) {
st[pos] = a[beg];
return;
}

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]);
}

int query(int beg, int end, int l, int r, int pos) {


pushdown(pos, beg, end);
if(l <= beg and r>= end) {
return st[pos];
} else if(l>end or r<beg) {
return MAX;

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;

for(int i=0; i<n; i++) {


cin>>a[i];
}
build(0, n-1, 0);
int q;
cin>>q;
while(q--) {
int l, r, v;
char ch;
cin>>l>>r;
ch = cin.get();
if(ch == ' ') {
cin>>v;
if(l <= r) {
update(0, n-1, l, r, v, 0);
} else {
update(0, n-1, l, n-1, v, 0);
update(0, n-1, 0, r, v, 0);
}
} else {
if(l <= r) {
cout<<query(0, n-1, l, r, 0) <<"\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:

We can solve this problem by using merge sort tree as follows:

● 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;

const int N = (int)(3e5+5);

vector<int> st[4*N];
int arr[N];

void build(int beg, int end, int pos) {


if(beg == end) {
st[pos].push_back(arr[beg]);
return;
}

build(beg, M, 2*pos+1);
build(M+1, end, 2*pos+2);

merge(st[2*pos+1].begin(), st[2*pos+1].end(), st[2*pos+2].begin(),


st[2*pos+2].end(), back_inserter(st[pos]));
}

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;

for(int i=0; i<n; i++) cin>>arr[i];

build(0, n-1, 0);

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--;

ans = query(0, n-1, a, b, c, 0);


cout<<ans<<"\n";
}

return 0;
}

22

You might also like