# Dynamic Programming in 5 easy steps - Examples - Two Robots

This time we will solve a HackerRank problem, rated as a medium in difficulty. It’s advised for you to go through a similar, but in my opinion easier problem described by me previously.

The problem statement is as follows:

You have a warehouse withReminder - the 5 easy steps are:Mcontainers filled with an infinite number of candies. The containers are arranged in a single row, equally spaced to be 1 meter apart. You also have 2 robots that can pick up 1 piece of candy and transport it between any two containers.

The robots take instructions in the form ofqueriesconsisting of two integers,Mand_{a}M, respectively. To execute a query, a robot travels to container_{b}M, picks up 1 candy, transports it to container_{a}M, and then stops at_{b}Muntil it receives another query._{b}

Calculate the minimum total distance the robots must travel to executeNqueries in order.

Note: You choose which robot executes each query.

- define subproblems, count number of subproblems
- guess (part of solution), count number of choices
- relate subproblem solutions, compute time per subproblem
- recurse + memoize OR build DP table bottom-up check subproblems acyclic/topological order, time = time per subproblem · number of subproblems
- solve original problem: = a subproblem OR by combining subproblem solutions ⇒ extra time

**Step 1.**Let’s try to figure out what our subproblems are. At a given point in solving the problem, what information do we want to have? First obvious thing: index of

*queries*. Next obvious thing: we want to optimize over total distance made by robots, therefore our DP function will return the minimum total distance made by robots at some point in our problem. Having that, we also probably will need positions of two robots. The situation looks like this:

**Step 2.**Now, we basically have two choices, A: proceed with the first robot or B: with the second robot. When

*A*- we add the distance of moving the

*r1*from it’s last place

*M*to

_{b(r1)}*M*

_{a(INDEX)}*,*otherwise

*B*: we add the distance of moving the

*r2*from it’s last place

*M*to

_{b(r2)}*M*. Additionally, in both cases we have to add the cost of moving

_{a(INDEX)}*M*to

_{a(INDEX) }*M*

_{b(INDEX).}**Step 3.**Writing the DP function is now straightforward:

DP(r1, r2, index) = min from:

A:

*M*

_{a(INDEX) - }*M*

_{b(r1) }*+ M*

_{b(INDEX) }*- M*

_{a(INDEX) }*+ DP(INDEX, r2)*

B:

*M*

_{a(INDEX) - }*M*

_{b(r2) }*+ M*

_{b(INDEX) }*- M*

_{a(INDEX) }*+ DP(r1,*

*INDEX*

*)*

However, having the index in DP function is redundant. We can decrease the time complexity of our solution by removing it - index is always the next item after max(

*r1*,

*r2*). Thus, DP function now looks like:

DP(r1, r2) = min from:

A:

*M*

_{a(max(r1, r2)+1) - }*M*

_{b(r1) }*+ M*

_{b(}

_{max(r1, r2)+1}

_{) }*- M*

_{a(}

_{max(r1, r2)+1}

_{)}

_{ }*+ DP(*

*max(r1, r2)+1, r2)*

B:

*M*

_{a(}

_{max(r1, r2)+1}

_{) - }*M*

_{b(r2) }*+ M*

_{b(}

_{max(r1, r2)+1}

_{) }*- M*

_{a(}

_{max(r1, r2)+1}

_{)}

_{ }*+*

*DP(*

*r1,*

*max(r1, r2)+1*)*M**

*M*, because that’s how many states which in the robots can be arranged. Time of computing one subproblem is constant, therefore the final complexity of this solution is O(M

^{2}).

**Step 4.**So is it finite algorithm? Yes, because the graph is traversed through two increasing values,

*r1*and

*r2*.

**Step 5.**The solution is in DP(-1, -1), assuming -1 denotes that the robot wasn’t used yet and the cost of moving it to

*M*is 0 (because it starts at this location).

_{x}## The code

1 |
using System; using System.Collections.Generic; namespace Algos { public class Solver { private readonly int _n; private readonly List<Tuple<int, int>> _queries; private int[][] _memo; // ReSharper disable once UnusedParameter.Local public Solver(int m, int n, List<Tuple<int, int>> queries) { _n = n; _queries = queries; } public int SolveCase() { _memo = new int[_n + 1][]; for (int i = 0; i < _n + 1; i++) _memo[i] = new int[_n + 1]; for (int i = _n; i >= 0; i--) { for (int j = _n; j >= 0; j--) { _memo[i][j] = -1; } } return Dp(-1, -1); } private int Dp(int r1, int r2) { if (r1 + 1 == _n || r2 + 1 == _n) return 0; if (_memo[r1 + 1][r2 + 1] != -1) return _memo[r1 + 1][r2 + 1]; var i = Math.Max(r1, r2) + 1; //r1 stays in place var r2Cover = 0; if (r2 != -1) r2Cover = Math.Abs(_queries[r2].Item2 - _queries[i].Item1); var d1 = Dp(r1, i); //r2 stays in place var r1Cover = 0; if (r1 != -1) r1Cover = Math.Abs(_queries[r1].Item2 - _queries[i].Item1); var d2 = Dp(i, r2); var queryCover = Math.Abs(_queries[i].Item1 - _queries[i].Item2); var min = Math.Min(r2Cover + d1 + queryCover, r1Cover + d2 + queryCover); _memo[r1 + 1][r2 + 1] = min; return min; } } class Solution { static void Main(string[] args) { //CaseSub0(); //Case0(); //Case1(); //Case2(); //RandomBigCase(); //return; var T = int.Parse(Console.ReadLine()); for (var i = 0; i < T; i++) { ProcessCase(); } } private static void ProcessCase() { var @case = Console.ReadLine().Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries); var M = int.Parse(@case[0]); var N = int.Parse(@case[1]); var queries = new List<Tuple<int, int>>(N); for (int i = 0; i < N; i++) { var query = Console.ReadLine().Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries); queries.Add(Tuple.Create(int.Parse(query[0]), int.Parse(query[1]))); } var solution = new Solver(M, N, queries).SolveCase(); Console.WriteLine(solution); } private static void CaseSub0() { var M = 2; var N = 1; var queries = new List<Tuple<int, int>> (); queries.Add(Tuple.Create(1, 2)); var solution = new Solver(M, N, queries).SolveCase(); Console.WriteLine("Casesub0 " + solution); } private static void Case0() { var M = 5; var N = 4; var queries = new List<Tuple<int, int>>(); queries.Add(Tuple.Create(1, 5)); queries.Add(Tuple.Create(3, 2)); queries.Add(Tuple.Create(4, 1)); queries.Add(Tuple.Create(2, 4)); var solution = new Solver(M, N, queries).SolveCase(); Console.WriteLine("Case0 " + solution); } private static void Case1() { var M = 4; var N = 2; var queries = new List<Tuple<int, int>> (); queries.Add(Tuple.Create(1, 2)); queries.Add(Tuple.Create(4, 3)); var solution = new Solver(M, N, queries).SolveCase(); Console.WriteLine("Case1 " + solution); } private static void Case2() { var M = 10; var N = 3; var queries = new List<Tuple<int, int>> (); queries.Add(Tuple.Create(2, 4)); queries.Add(Tuple.Create(5, 4)); queries.Add(Tuple.Create(9, 8)); var solution = new Solver(M, N, queries).SolveCase(); Console.WriteLine("Case2 " + solution); } private static void RandomBigCase() { var M = 1000; var N = 1000; var queries = new List<Tuple<int, int>> (); var r = new Random(666); while (queries.Count != N) { var from = r.Next(1, M + 1); var to = r.Next(1, M + 1); var t = Tuple.Create(from, to); if (!queries.Contains(t)) queries.Add(t); } var solution = new Solver(M, N, queries).SolveCase(); Console.WriteLine("RandomBigCase " + solution); } } } |