cpsc 490 problem solving in computer science
play

CPSC 490: Problem Solving in Computer Science 1 Range-minimum query - PowerPoint PPT Presentation

Lecture 12: Segment trees Henry Xia, Brandon Zhang based on CPSC 490 slides from 2014-2018 2019-02-28 University of British Columbia CPSC 490: Problem Solving in Computer Science 1 Range-minimum query Given an array A of N 10 6 numbers,


  1. Lecture 12: Segment trees Henry Xia, Brandon Zhang based on CPSC 490 slides from 2014-2018 2019-02-28 University of British Columbia CPSC 490: Problem Solving in Computer Science

  2. 1 Range-minimum query Given an array A of N ≤ 10 6 numbers, answer Q ≤ 10 6 queries of the form “What is the minimum number in the subarray A [ l .. r ] ?”

  3. Recall that we solved this using binary jumping: 2 Range-minimum query f ( i , p ) = the minimum number in A [ i .. i + 2 p − 1 ] . f ( i , 0 ) = . . . , f ( i , p ) = . . . min A [ l .. r ] = min { f ( l , q ) , f ( r − 2 q + 1 , q ) } .

  4. following type: Naive solution: O 1 per update, O N per query. Binary jumping doesn’t work! Very slow to update. Let’s try a difgerent way of splitting our array into chunks... 3 Range-minimum query, point updates Given an array A of N ≤ 10 6 numbers, answer Q ≤ 10 6 queries and updates of the 1. Output the minimum number in the subarray A [ l .. r ] . 2. Update A [ i ] to k .

  5. Binary jumping doesn’t work! Very slow to update. following type: Let’s try a difgerent way of splitting our array into chunks... 3 Range-minimum query, point updates Given an array A of N ≤ 10 6 numbers, answer Q ≤ 10 6 queries and updates of the 1. Output the minimum number in the subarray A [ l .. r ] . 2. Update A [ i ] to k . Naive solution: O ( 1 ) per update, O ( N ) per query.

  6. Binary jumping doesn’t work! Very slow to update. following type: Let’s try a difgerent way of splitting our array into chunks... 3 Range-minimum query, point updates Given an array A of N ≤ 10 6 numbers, answer Q ≤ 10 6 queries and updates of the 1. Output the minimum number in the subarray A [ l .. r ] . 2. Update A [ i ] to k . Naive solution: O ( 1 ) per update, O ( N ) per query.

  7. Let’s assume that N is a power of two (if not, pad the array until it is). We’ll make a perfect binary tree where each node represents a segment of the array. • The leaves represent segments with one element. • The root represents the whole array. • Each non-leaf represents the union of the segments of its two children. Each node in the tree will store the answer for that segment (in our range-minimum query problem, the minimum for that segment). 4 Segment tree design • Perfect binary tree with lg N layers, so 1 + 2 + 4 + · · · + N = 2 N − 1 nodes in total.

  8. Let’s assume that N is a power of two (if not, pad the array until it is). We’ll make a perfect binary tree where each node represents a segment of the array. • The leaves represent segments with one element. • The root represents the whole array. • Each non-leaf represents the union of the segments of its two children. Each node in the tree will store the answer for that segment (in our range-minimum query problem, the minimum for that segment). 4 Segment tree design • Perfect binary tree with lg N layers, so 1 + 2 + 4 + · · · + N = 2 N − 1 nodes in total.

  9. How do we effjciently compute the answer for a segment? The minimum of our segment is the minimum of the values at our two children! 5 Segment tree design If we’re at a node representing the segment [ l , r ] , then our leħt child has the minimum of the segment [ l , m ] and our right child has the minimum of the segment [ m + 1 , r ] (where m = ( l + r ) / 2).

  10. Obviously we need to update the leaf node for the i th element. The parent of this node depends on this, so we’ll need to update it as well, then update its parent, and so on until we reach the root. 6 Segment tree design How do we perform updates (what do we need to change when we update A [ i ] to k )? O (log N ) to update, since we only update on the path from leaf to root in a tree with depth lg N .

  11. 7 Point update, visualized

  12. 7 Point update, visualized

  13. 7 Point update, visualized

  14. 7 Point update, visualized

  15. 7 Point update, visualized

  16. We can decompose this range into subranges in the segment tree. We’ll find the subranges recursively: • Start at the root and recurse downards. (in this case, take the min). 8 Segment tree design What about querying for the minimum element in the range [ x , y ] ? • Suppose the node we’re at represents the segment [ l , r ] . There are 2 cases: • If [ l , r ] ⊂ [ x , y ] then return the value of the node. • Otherwise, recurse on the children that aren’t disjoint from [ x , y ] and combine the answer We can prove that at each level, we’ll consider at most 4 nodes ⇒ O (log n ) per query.

  17. 9 Range query, visualized

  18. 9 Range query, visualized

  19. 9 Range query, visualized

  20. 9 Range query, visualized

  21. 9 Range query, visualized

  22. 9 Range query, visualized

  23. 9 Range query, visualized

  24. 9 Range query, visualized

  25. 10 Segment tree initialization • Naive way: call update for each element ( O ( N log N ) ) • Faster way: fill from the leaves upwards, layer by layer ( O ( N ) ) Overall, our RMQ problem runs in O ( N + Q log N ) .

  26. How do we implement these operations? First, to store the tree: • Store the tree nodes in an array T of length 2 N . 11 Segment tree implementation • The root is T [ 1 ] . • For a node i , its children are 2 i and 2 i + 1, and its parent is ⌊ i / 2 ⌋ . • The leaf nodes are T [ N .. 2 N − 1 ] .

  27. 12 1 12 11 2 3 10 9 4 8 5 6 7 Segment tree implementation T = array of length 2*N # N is a power of two def pull(i): # update node i with its children's values T[i] = min(T[2*i], T[2*i+1]) def init(A): # initialize the segment tree with array A # fill leaves for i = 0 .. N-1: T[N+i] = A[i] # fill rest of tree for i = N-1 .. 1: pull(i)

  28. 13 5 9 8 2 7 3 6 4 1 Segment tree implementation def update(i, k): # change the ith element to k # update the leaf v = N+i T[v] = k # propagate changes up to the root v = v / 2 while v > 0: pull(v) v = v / 2 # v = parent(v)

  29. 14 6 11 10 2 9 3 4 8 7 5 1 Segment tree implementation def query(x, y): # query for the min value in [x, y] return query(x, y, 1, 0, N-1) def query(x, y, i, l, r): # i is the node index, and its segment is [l, r] if r < x or y < l: # [l, r] completely outside [x, y] (irrelevant) return ∞ # the identity element for min if x <= l and r <= y: # [l, r] contained in [x, y] (don't need to recurse) return T[i] # recurse on children m = (l + r) / 2 return min(query(x, y, 2*i, l, m), query(x, y, 2*i+1, m+1, r))

  30. We can store any kind of data in our nodes, and combine them in (almost) any way (e.g. Segment trees are very flexible! gcd, sets and set union, polynomials and addition). 15 Segment tree

  31. Answer Q queries on an array A of the following types: 16 Problem 1 – Classic problem 1. Update A [ i ] to k . 2. Query for the maximum-sum subarray in A [ l .. r ] .

  32. In a node, store four values: • The sum of all the elements in the segment. • The maximum sum of a subarray which starts at the leħt endpoint. • The maximum sum of a subarray which ends at the right endpoint. • The maximum sum of any subarray. At a node, its best subarray could lie entirely in the leħt child, entirely in the right child, or it could go across. Use the statistics we store for our children to compute our own! 17 Problem 1 – Solution sketch

  33. Source: Codeforces 61E 18 Problem 2 – Increasing triples Input : an array A with N ≤ 10 6 integers. Output : the number of triples of indices ( i , j , k ) satisfying i < j < k and A [ i ] < A [ j ] < A [ k ] .

  34. • Let’s count these separately in two passes. Idea: for each index j , count the number of triples where j is the middle index. • We’ll store a segment tree indexed by the values. Then, a range sum will tell us the number of array elements in that range. 19 Problem 2 – Solution • For all j , we need to count pairs ( i , k ) such that i < j < k and A [ i ] < A [ j ] < A [ k ] . • Let less ( j ) be the number of indices i with i < j and A [ i ] < A [ j ] , and let greater ( j ) be the number of indices k with j < k and A [ j ] < A [ k ] . • We just need to compute less ( j ) · greater ( j ) for each j , and sum them up. • For less ( j ) , process j from leħt to right. Initially, the segment tree stores all 0’s. • Add 1 to the A [ j ] th index in the segment tree. Then, less ( j ) is the range sum from [ 0 , A [ j ] − 1 ] . • greater ( j ) is similar (going from right to leħt). Time complexity: O ( n log n )

Recommend


More recommend