- James S. Plank
- October, 2012

The problem description is
here..
There is a driver in
**Cs-Main.cpp**.

Like many problems in Topcoder, the key to this one is figuring out how to map
it to a problem that you know how to solve already. In this case, it smells like
a shortest path problem, where the classes will be the edges.
The question is how to convert the problem to the proper
graph? If you want to think about it on your own, go ahead, but
*don't write any code until you have figured out how to convert the problem
to a graph with an unweighted shortest path solution.*

It makes sense to create nodes that are indexed by [T,P], where T is the theoretical
value that you have achieved, and P is the practical value. You will start
at node [0,0], and you want to get to a node [T,P] where both T and P are
≥ **skillBound**.

Classes will compose the edges. Let's ignore when classes expire for now. There is an edge from node [T,P] to node [T',P'] as long as:

- T' is equal to T or T+1.
- P' is equal to P or P+1.
- There is a class
*c*such that*theoreticalValue[c]*≤ T' and*practicalValue[c]*≤ P'.

Because we are ignoring expiration, examples 1 and 2 map to:

After you create the graph, you want to find the shortest path from [0,0] to [T,P] where
both T and P are ≥ **skillBound**. If there are multiple paths, as in example 1, you want
the lexicographically smaller one, which means you perform the BFS in ascending order of
edges. In Example 1, when you reach node [1,1], you process the edge to [1,2] first,
because class 0 is smaller than class 1.

That's all well and good, but there is one subtlety -- when you reach a node,
how do you know whether a class has expired? The answer is that the shortest path to
that node is the quickest way that you can reach that node -- if the shortest path is *s*,
then you can get to that node by month *s*. For example, in both examples 1 and
2, the shortest path to node [1,1] is 1, so you can get to that node by month 1. To
leave the node, you have to take a class that expires in a month greater than one.
In example 2, there are no such classes, so you cannot leave that node.

- Push node [0,0] onto a BFS queue.
- Process the queue -- when you process a node, you know the shortest path to the node. That allows you to run through all the classes in order and see which ones have not expired. For all unexpired classes, use the ones that will take you from the node to a node that is not already on the BFS queue. Add those new nodes to the BFS queue. When you reach a node that has the required T and P, you're done!
- Of course, you have maintain the path length, a back pointer and you have to record which class got you to each node. That allows you to construct the final return value when you're done.

class Node { public: int T; int P; int back_class; Node *back_node; int path_length; }; |

**Back_class** is the number of the class that got me to this node.
**Back_node** is a pointer to the previous node in the shortest path.
**path_length** is the length of the shortest path to each node.

I created a doubly-indexed vector of pointers to nodes, and then resized it so
that all values of T and P between 0 and 50 are defined. I created nodes for
each of these, set their values of T and P, and sentinelized **back_node** to
NULL and **path_length** to -1. You'll note that I end up creating more nodes
than I really need. For example, if **skillBound** is 10, there will never
be nodes where both T and P are greater than 10. However, I don't care -- it's
easy to create the nodes in this way.

Then I wrote a BFS routine that first pushes node [0,0] on the queue and then
processes the queue. For each node that I process, I traverse the classes and
determine if there is an edge from that node to another. That's a little tricky.
If there is an edge, then I check to make sure that the node is not already
on the queue by testing to see if **path_length** is equal to -1. If it
passes that test, I set **path_length** and push it on the queue.

If I get to an ending node, where T and P are big enough, I simply print the path length and exit. Note, I'm ignoring expiration here. I test this first program to make sure that it makes sense on the examples:

UNIX>Next, I add expiration. That kills example 2:a.out 0On node [0,0] Considering Class 0: [1,1] Pushing [1,1] from [0,0] - path length 1 On node [1,1] Path found: Length = 1 UNIX>a.out 1On node [0,0] Considering Class 0: [1,2] Considering Class 1: [2,1] Considering Class 2: [1,1] Pushing [1,1] from [0,0] - path length 1 On node [1,1] Considering Class 0: [1,2] Pushing [1,2] from [1,1] - path length 2 Considering Class 1: [2,1] Pushing [2,1] from [1,1] - path length 2 Considering Class 2: [1,1] On node [1,2] Considering Class 0: [1,2] Considering Class 1: [2,1] Pushing [2,2] from [1,2] - path length 3 Considering Class 2: [1,1] On node [2,1] Considering Class 0: [1,2] Considering Class 1: [2,1] Considering Class 2: [1,1] On node [2,2] Path found: Length = 3 UNIX>a.out 2 | tail -n 1Path found: Length = 3 /* This is right because I'm ignoring expiration. */ UNIX>a.out 3 | tail -n 1Path found: Length = 3 UNIX>a.out 4 | tail -n 1Path found: Length = 7 UNIX>a.out 5 | tail -n 1Path found: Length = 2 UNIX>

UNIX>Finally, I add back edges and the back classes, calculate and return the path:a.out 1 | tail -n 1Path found: Length = 3 UNIX>a.out 2 | tail -n 1No path found UNIX>

UNIX>a.out 0{0} UNIX>a.out 1{2 0 1} UNIX>a.out 2UNIX>a.out 3{2 1 0} UNIX>a.out 4{0 1 2 3 4 5 6} UNIX>a.out 5{4 3} UNIX>

If you think about running time complexity, there are 2601 nodes, and each time you process a node, you run through up to 50 classes. 50 times 2601 equals 130,050, and you get roughly 2,000,000 operations with Topcoder -- it should easily run within the time bound.

My code is in **CsCourses.cpp**.