Range Queries Part 2: Segment Trees and Lazy Propagation Lucca - - PowerPoint PPT Presentation

range queries
SMART_READER_LITE
LIVE PREVIEW

Range Queries Part 2: Segment Trees and Lazy Propagation Lucca - - PowerPoint PPT Presentation

Range Queries Part 2: Segment Trees and Lazy Propagation Lucca Siaudzionis and Jack Spalding-Jamieson 2020/02/06 University of British Columbia Announcements A2 is due Sunday!!! Jack will be hosting additional office hours from


slide-1
SLIDE 1

Range Queries

Part 2: Segment Trees and Lazy Propagation

Lucca Siaudzionis and Jack Spalding-Jamieson 2020/02/06

University of British Columbia

slide-2
SLIDE 2

Announcements

  • A2 is due Sunday!!!
  • Jack will be hosting additional office hours from 3:30-4:30 on Friday in X237.
  • Written Report requirements and rubric will be posted soon.

1

slide-3
SLIDE 3

Survey Talk

  • Details about the written report?
  • Slides logistics.
  • Assignment is too long.
  • Assignment is too hard.
  • Judge’s feedback is nearly useless.
  • Lecture speed.

2

slide-4
SLIDE 4

Remember this Problem?

Given an array A of N ≤ 106 numbers, answer Q ≤ 106 queries of the form “What is the minimum number in the subarray A[l..r]?” This is called Range Minimum Query (RMQ).

3

slide-5
SLIDE 5

Range Minimum Query

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

4

slide-6
SLIDE 6

Range Minimum Query, Point Updates

Given an array A of N ≤ 106 numbers, answer Q ≤ 106 queries and updates of the following type:

  • 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. Binary jumping doesn’t work! It’s very slow to update instead. Let’s try a different way of splitting our array into chunks...

5

slide-7
SLIDE 7

Segment Tree Design

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.
  • Perfect binary tree with lg N layers, so 1 + 2 + 4 + · · · + N = 2N − 1 nodes in total.

Each node in the tree will store the answer for that segment (in our range-minimum query problem, the minimum for that segment).

6

slide-8
SLIDE 8

Segment Tree Design

How do we efficiently compute the answer for a segment? If we’re at a node representing the segment [l, r], then our left 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). The minimum of our segment is the minimum of the values at our two children!

7

slide-9
SLIDE 9

Segment Tree Design

How do we perform updates (what do we need to change when we update A[i] to k)? Obviously we need to update the leaf node for the ith 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. O(log N) to update, since we only update on the path from leaf to root in a tree with depth lg N.

8

slide-10
SLIDE 10

Point Update, Visualized (Sum Segment Tree)

9

slide-11
SLIDE 11

Point Update, Visualized (Sum Segment Tree)

10

slide-12
SLIDE 12

Point Update, Visualized (Sum Segment Tree)

11

slide-13
SLIDE 13

Point Update, Visualized (Sum Segment Tree)

12

slide-14
SLIDE 14

Point Update, Visualized (Sum Segment Tree)

13

slide-15
SLIDE 15

Segment Tree Design

What about querying for the minimum element in the range [x, y]? We can decompose this range into subranges in the segment tree. We’ll find the subranges recursively:

  • Start at the root and recurse downards.
  • 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

(in this case, take the min).

We can prove that at each level, we’ll consider at most 4 nodes ⇒ O(log n) per query.

14

slide-16
SLIDE 16

Range Query, Visualized

We want to compute the sum of all the values in the highlighted interval.

15

slide-17
SLIDE 17

Range Query, Visualized

16

slide-18
SLIDE 18

Range Query, Visualized

17

slide-19
SLIDE 19

Range Query, Visualized

18

slide-20
SLIDE 20

Range Query, Visualized

19

slide-21
SLIDE 21

Range Query, Visualized

20

slide-22
SLIDE 22

Range Query, Visualized

21

slide-23
SLIDE 23

Range Query, Visualized

22

slide-24
SLIDE 24

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

23

slide-25
SLIDE 25

Segment Tree Implementation

How do we implement these operations? First, to store the tree:

  • Store the tree nodes in an array T of length 2N.
  • The root is T[1].
  • For a node i, its children are 2i and 2i + 1, and its parent is

i

2

  • .
  • This is just like binary heaps!
  • The leaf nodes are T[N..(2N − 1)].
  • The intermediate nodes are T[1..(N − 1)].

24

slide-26
SLIDE 26

Segment Tree Implementation

1

T = array of length 2*N # N is a power of two

2 3

def pull(i): # update node i with its children's values

4

T[i] = min(T[2*i], T[2*i+1])

5 6

def init(A): # initialize the segment tree with array A

7

# fill leaves

8

for i = 0 .. N-1:

9

T[N+i] = A[i]

10

# fill rest of tree

11

for i = N-1 .. 1:

12

pull(i)

1

const int MAXN = 100000;

2

int bit[MAXN+1];

25

slide-27
SLIDE 27

Segment Tree Implementation

1

def update(i, k): # change the ith element to k

2

# update the leaf

3

v = N+i

4

T[v] = k

5

# propagate changes up to the root

6

v = v / 2

7

while v > 0:

8

pull(v)

9

v = v / 2 # v = parent(v)

26

slide-28
SLIDE 28

Segment Tree Implementation

1

def query(x, y): # query for the min value in [x, y]

2

return query(x, y, 1, 0, N-1)

3 4

def query(x, y, i, l, r): # i is the node index, and its segment is [l, r]

5

if r < x or y < l: # [l, r] completely outside [x, y] (irrelevant)

6

return infinity # the identity element for min

7

if x <= l and r <= y: # [l, r] contained in [x, y]

8

return T[i] # (don't need to recurse)

9

# recurse on children

10

m = (l + r) / 2

11

return min(query(x, y, 2*i, l, m), query(x, y, 2*i+1, m+1, r))

27

slide-29
SLIDE 29

Segment Tree

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

28

slide-30
SLIDE 30

Discussion Problem 1

Answer Q queries on an array A of the following types:

  • 1. Update A[i] to k.
  • 2. Query for the maximum-sum subarray in A[l..r].

29

slide-31
SLIDE 31

Discussion Problem 1 – Insight

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 left 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 left child, entirely in the right child, or it could go across. Use the statistics we store for our children to compute our own!

30

slide-32
SLIDE 32

Range Updates, Range Queries

Given an array A, answer many queries of the following types:

  • 1. Output A[l..r].
  • 2. Add x to all the elements in the range A[l..r].

How can we handle both at once?

31

slide-33
SLIDE 33

Range Updates, Range Queries

Idea is similar to range updates, point queries, but now we need to be careful about managing the added values in segments. Let’s be lazy!

32

slide-34
SLIDE 34

Lazy Propagation

  • To add x to everything in [l, r], we’ll update the same O(log n) segments that we look at

in a query.

  • We’ll also update a lazy value for these segments: lazy(i) = the amount that was added

to this segment that we need to update our children with.

  • When we need to visit children of this node later, we’ll “push” the lazy update down to its

children.

33

slide-35
SLIDE 35

Lazy Propagation, Visualized

34

slide-36
SLIDE 36

Lazy Propagation, Visualized

35

slide-37
SLIDE 37

Lazy Propagation, Visualized

36

slide-38
SLIDE 38

Lazy Propagation, Visualized

37

slide-39
SLIDE 39

Lazy Propagation, Visualized

38

slide-40
SLIDE 40

Lazy Propagation, Visualized

39

slide-41
SLIDE 41

Lazy Propagation, Visualized

40

slide-42
SLIDE 42

Lazy Propagation, Visualized

41

slide-43
SLIDE 43

Lazy Propagation, Visualized

42

slide-44
SLIDE 44

Lazy Propagation, Visualized

43

slide-45
SLIDE 45

Lazy Propagation, Visualized

44

slide-46
SLIDE 46

Lazy Propagation, Visualized

45

slide-47
SLIDE 47

Lazy Propagation, Visualized

46

slide-48
SLIDE 48

Lazy Propagation, Visualized

47

slide-49
SLIDE 49

Lazy Propagation, Visualized

48

slide-50
SLIDE 50

Lazy Propagation, Visualized

49

slide-51
SLIDE 51

Lazy Propagation, Visualized

50

slide-52
SLIDE 52

Lazy Propagation, Visualized

51

slide-53
SLIDE 53

Lazy Propagation, Visualized

52

slide-54
SLIDE 54

Lazy Propagation, Visualized

53

slide-55
SLIDE 55

Lazy Propagation, Visualized

54

slide-56
SLIDE 56

Lazy Propagation – Implementation

1

T = array of 2*N elements

2

lazy = array of 2*N elements, initialized to null

3 4

def pull(i):

5

T[i] = T[2*i] + T[2*i+1]

6 7

def push(i): # update node i's children with the lazy value

8

if lazy[i] is not null:

9

T[2*i] += lazy[i] * length(2*i)

10

T[2*i+1] += lazy[i] * length(2*i+1)

11

lazy[2*i] += lazy[i]

12

lazy[2*i+1] += lazy[i]

13

lazy[i] = null

55

slide-57
SLIDE 57

Lazy propagation – implementation

1

def update(x, y, k): # add k to A[x..y]

2

return update(x, y, k, 1, 0, N-1)

3 4

def update(x, y, k, i, l, r):

5

if r < x or y < l: return # this range is irrelevant

6

if x <= l and r <= y: # range is contained: update the lazy tag

7

T[i] += k * length(i)

8

lazy[i] += k

9

return

10

m = (l + r) / 2

11

push(i) # accessing children, so need to push!

12

update(x, y, k, 2*i, l, m)

13

update(x, y, k, 2*i+1, m+1, r)

14

pull(i) # update ourselves

56

slide-58
SLIDE 58

Lazy propagation – implementation

1

def query(x, y):

2

return query(x, y, 1, 0, N-1)

3 4

def query(x, y, i, l, r):

5

if r < x or y < l: # range is irrelevant

6

return 0

7

if x <= l and r <= y: # range is contained

8

return T[i]

9

m = (l + r) / 2

10

push(i) # accessing children, so need to push!

11

return query(x, y, 2*i, l, m) + query(x, y, 2*i+1, m+1, r)

57

slide-59
SLIDE 59

Segment Trees - Overview

Segment trees allow you to solve all sorts of problems. There are many things you have to consider even when solving simple segment tree problems:

  • What datatype is stored in the original array?
  • What is the operation used for our range updates?
  • What is the operation used for our range queries?
  • What do we need to store in our segment tree?
  • How do we combine answers in our segment tree?
  • How do we apply our lazy? Do we need to make use of the length?

58

slide-60
SLIDE 60

Discussion Problem 2: Slightly different problem

Given an array A, answer many queries of the following types:

  • 1. Output A[l..r].
  • 2. Assign the value x to all the elements in the range A[l..r].

59

slide-61
SLIDE 61

Discussion Problem 2: Slightly different problem – Insight

Use a lazy segment tree! The lazy updates need to be handled a little differently:

  • Instead of summing the lazy values, we’ll assign them.
  • When pushing, instead of adding the lazy, we’ll set the tree value to the lazy (times the

length).

60

slide-62
SLIDE 62

Fun Things to do This Weekend

Jack’s recommendation: Betrayal at House on the Hill

61

slide-63
SLIDE 63

Fun Things to do This Weekend

Lucca’s recommendation: Whiplash

62