Problem Link
Author: Bhuvnesh Jain
Tester: Bhuvnesh Jain
Editorialist: Bhuvnesh Jain
Difficulty
MEDIUM
Prerequisites
Recursion, Bitmasks, Dynamic Programming.
Problem
Find the minimum time taken to cross the river by N people if we can allow almost 2 people to use a boat such that the rowing time is equal to the minimum of the rowing time of both the people. Note that, slowing rowing time is given by larger integers.
Explanation
First of all, any type of greedy algorithm will only pass the first subtask and not any other. As the constraints are small and greedy solution will not work, we will try to come up with a dynamic programming solution.
Let us assume we denote the side where all the persons are initially standing by left and their destination, i.e. where they want to reach by right.
The first observation is that we will always try to send 2 people in the boat. This is because the slower person rowing time will then not contribute to the answer. The next observation is that once few people have crossed the river and reached “right”, the boat will be brought to the left side by the person whose rowing time is the minimum among the persons at the right side. Next is that we can represent each person by a “bit-mask”. The “left” and “right” bitmask denote that if the {i}^{th} bit is one, then the {i}^{th} person is present there else he is not present. The last observation is that the person can be present either in the “left” side or “right” side, not both ways. Thus, we only care about “left” side masks. The “right” mask can be calculated from the “left” ones, by setting the bits to 1 which are not present in “left”.
With all these observations, we try to build a recursive solution as follows: (in the code, “turn” means, whether we send people from right to left or reverse)
//call with recurse((2^n)-1, 0)
void recurse(LEFTMASK, turn):
if (LEFTMASK == 0):
return 0 //we transferred all the people
RIGHTMASK = ((2^n)-1) ^ LEFTMASK
if (turn == 1): //we want to transfer people to the left
min_row = infinity, person = -1
for i in [0 .. n-1]:
if (RIGHTMASK & (2^i)):
if (min_row > row[i]):
min_row = row[i]
person = i
return row[person] + recurse(LEFTMASK ^ (2^person), turn ^ 1)
else: //we want to transfer people to the right
if (number of set bits in LEFTMASK == 1):
//only 1 person is left)
for i in [0 .. n-1]:
if (LEFTMASK & (2^i)):
return row[i] + recurse(left ^ (2^i), turn ^ 1)
else:
//try every pair of people by sending them to right)
ans = infinity
for i in [0 .. n-1]:
for j in [i+1 .. n-1]:
if ((LEFTMASK & (2^i)) && (LEFTMASK & (2^j)):
val = max(row[i], row[j])
val += recurse(LEFTMASK^(2^i)^(2^j), turn ^ 1)
ans = min(ans, val)
return ans
To optimise the above code for the full score, we can use dynamic programming so that each state is visited only once. This is also known as “top-down DP” or “recursion with memoization”.
Time Complexity
O(2^N * N * N)
Space Complexity
O(2^N)