Binary Lifting
What’s this ?
Binary Lifting is a simple computation technique which makes use of 2 simple facts :
- Any number can be represented as sum of some powers of 2’s
- Dynamic Programming
I will be using an example problem to demonstrate the binary lifting process . The problem is to find the kth ancestor of a node in a tree .
Spoiler
Although I will be using a tree for this , you can use this same idea outside trees also . Try to find use cases after reading the whole article :pOkay , How do you do that ?
First things first .
- Any number can be represented as sum of some powers of 2’s
Taking advantage of this , if we want to know kth ancestor , we can represent taking k
steps as taking no more than log(k)
steps . Now how do we solve the problem by taking log(k)
steps ? After all taking log(k)
steps ain’t the same as taking k
steps :smirk: Otherwise we would have done that earlier !
Here’s how we do this ,
Imagine this is the tree . The parent of 1
is 2
, parent of 2
is 3
and so on . Now , what we do is basically use the information known so far to compute my curent step . Suppose we want to know the 2nd ancestor of 1
. You can easily see that this is nothing other than 1st ancestor (i.e his parent in this case) of 2
. We can do this as we already know the information of 1st ancestors of all the nodes . This is how we solve our problem by taking advantage of the information we know so far . The fancy name of what we did so far is Dynamic Programming .
This is what we did so far in pseudocode
1 | # ancestor[i][j] = 2^j th ancestor of ith node |
Now , we know all 2 j th ancestors . How to find the kth ancestor where k is not a power of 2 . Simple ! Just you can represet
1 | 6 = 2^2 + 2^1 |
the same way you can find the kth ancestor . The pseudocode is as follows :
1 | # node is the one that we are finding the ancestor of aka the one we are lifting |
Woah , Where is this gonna help ?
Binary Lifting_ has a lot of use cases . Most common is that in finding _Least Common Ancestor (LCA) . But who says this is the only thing that you can do ! You can do like path queries (minimum in a path) and many more !
And who says you can only use this ideas in trees ? You can use this same idea in Sparse Table , Binary Index Trees and in many more cases !
Practice Problems
-
Idea
We can use an additional array to track the minimum cost.
1
2
3
4
5
6
7
8
9
10
11
12
13
14# ancestor[i][j] = 2^j th ancestor of ith node
# minimum[i][j] = minimum distance upto 2^j th ancestor of ith node
for i in nodes:
ancestor[i][0] = parent[i]
minimum[i][0] = cost[{i,parent[i]}]
for j in 1 to log(k):
for i in nodes:
middle_man = ancestor[i][j-1] # this guy helps me to calculate my current step
ancestor[i][j] = ancestor[middle_man][j-1] # 2^(j-1) th ancestor's 2^(j-1) th ancestor is what we want
minimum[i][j] = min(minimum[i][j-1],minimum[middle_man][j-1])
# here , minimum[i][j-1] is the path from ith node to his 2^(j-1) th ancestor and minimum[middle_man][j-1] is the path from 2^(j-1) th ancestor to its 2^(j-1) th ancestor. Basically the minumum of the 2 paths indicated in the picture in red arrows
Applications (First complete the practice problems)
LCA (Lowest Common Ancestor)
Idea
The idea is to :
- First make the levels same
- We do this by binary lifting the deeper node
- Second , we lift both the nodes up as long as both of their parents are not same
- We do this by binary lifting both the nodes
Code
1 |
|
Path Query (Minimum/Maximum) without any update
Idea
We can use an additional array to track the minimum cost.
1 | # ancestor[i][j] = 2^j th ancestor of ith node |
Code
1 |
|
Sparse Table
Idea
Check cp-algorithms