## Question 1: 10 points

This is a nuts and bolts Topcoder D2, 250-point problem (SRM 354). Obviously there are many solutions, but they all involve testing each plan to see if its valid, and if so, counting the number of C's in the string; then returning the minimum. The code below uses trails.size()+1 as a sentinel for the minimum value. It also uses trails.size()+1 to find valid plans -- when the program discovers that a plan is not valid, the number of campsites is set to trails.size()+1, so that the plan will not be counted. The solution is in Trekking.cpp. There is also a main in Trekking-Main.cpp.

 ```int Trekking::findCamps(string trail, vector plans) { int min, i, nc, j; min = trail.size()+1; for (i = 0; i < plans.size(); i++) { nc = 0; for (j = 0; j < trail.size(); j++) { if (plans[i][j] == 'C') { if (trail[j] == '.') { nc++; } else { nc = trail.size()+1; } } } if (nc < min) min = nc; } if (min == trail.size()+1) return -1; return min; } ```

### Grading

10 points. This is one of those where I start with 10 points if you have the right basic structure, and I deduct points for problems, like:
• Not being able to return zero as a legal value.
• Returning from the program prematurely.
• Uninitialized variables.
• Too much bad syntax

## Question 2: Ten Points

The examples help -- if there is a cycle anywere between s and t, then there is an infinite number of paths. You need to think about this a little. Below are four graphs that contain cycles:

 Example from the problem. Cycle on the way from s to t that doesn't include s. Cycle involving s that's not on the way from s to t. Cycle not involving s that's also not on the way from s to t.

In the first two cases, there are clearly an infinite number of paths.

In the third, there are still an infinite number of paths, because you can travel that cycle any number of times before heading to t.

In the fourth, there are not infinite paths because you can't get to t if you go to nodes B or E.

So, your problem solving should take multiple steps. The first is to see if s is involved in any cycles. That's DFS, straight from the lecture notes: O(E). You could say O(V+E); however, since all nodes are reachable from s, you know that E ≥ V+1, so O(E) suffices.

The second step should be to mark all nodes that can be in paths from s to t. You can do that with another DFS, where the "visiting" action is "can I reach t":

 ```can_i_reach_t(node n) { if (n->i_can_reach_t != -1) return n->i_can_reach_t; n->i_can_reach_t = 0; for (i = n->edges.begin(); i != n->edges.end(); i++) { if (can_i_reach_t(n->edges[i])) n->i_can_reach_t = 1; } return n->i_can_reach_t; } ```

Initialize by setting i_can_reach_t to -1 for all nodes but t, which should be set to 1. This is O(E).

When you're done, delete any nodes that are not on paths to t. This is O(V).

Next, do a cycle detection pass using DFS, and if you detect any cycle, return -1: O(E).

Finally, perform a topological sort. When you do this, have each node store the number of paths to it from s. All nodes have that value initialized to zero, except s, which is initialized to one. When you visit a node n, in the topological sort, traverse its edges and add n's number of paths to each "to" node on the edge. We did this exact algorithm when I taught Toplogical sort. This is also O(E).

You can use dynamic programming for this last step, but it's more of a pain.

Voila -- problem solved in linear time with the help of CS302!

### Grading

I was expecting this to be a hard problem. I was projecting that:
• Everyone would see the need for cycle detection, and would use DFS.
• Many would see the connection between topological sort and the solution, once the cycles were removed.
• Some would guess that it's dynamic programming.
• One or two would see the problem with cycles that are not on any path from s to t.
In reality, I was right about #1 and wrong about the rest. Two people saw the connection between topological sort. No one saw a good DP solution or the issue with cycles not on the path. Many panicked and tried to use network flow. So it goes.

My grading was:

• DFS to remove cycles: 3 points.
• Proper running time for DFS: 1 point.
• Topological sort or dynamic programming to count paths: 3 points.
• Proper running time for that (O|E|): 1 point
• Handling of cycles that are not on the path from source to sink: 2 points.
Other solutions that relied on enumeration or actually traversing every path, instead of the topological sort/DP, got up to two points total if they made some sense.

## Question 3: Ten points

All lecture note stuff:
• Dijkstra's algorithm: This solves the shortest path problem in a weighted graph. To be precise: Given a weighted graph (directed or undirected), a starting node s and an ending node t, find the shortest path from s to t.

• Prim's algorithm: This solves the minimum spanning tree problem. Given a weighted, undirected graph, find the spanning tree whose sum of edges is minimum. A spanning tree is a set of V-1 edges such that there is no cycle (or such that there is a path between every pair of nodes).

• BFS: This solves the same problem as Dijkstra's algorithm, except the graph is unweighted.

• Kruskal's algorithm: This solves the same problem as Prim's algorithm.

### Grading

Three points each for the first two, and two points each for the second two. If you saw "Not precise," then your answer was not specific enough. For example, "finds the distance of each node from the source" is not precise enough for Dijkstra. Why? Well, what kind of graph? Directed? Undirected? Weighted? Unweighted? What's the "source" Also, it finds the minimum distance.

"Combination of correct and incorrect answers" should be self-explanatory. For example, if you said that Dijkstra's algorithm "counts the number of connected components and finds the minimum distance of each node from a given source," then you get points off for the first part because it's not correct.

## Question 4: 12 Points

Step 1: Spot the recursion. With the fibonacci numbers, that's easy:

fib(0) = fib(1) = 1
fib(n) = fib(n-1) - fib(n-2)
The C++ version looks very similar:

 ```int Fib::fib(int n) { if (n == 0 || n == 1) return 1; return fib(n-1) + fib(n-2); } ```

Step 2: Memoize (use a cache) to avoid repeating identical recursive calls. In this, we add a cache to the class definition. It is a vector of ints. We size it to the maximum n+1 that we want and set all values equal to -1. Then the method simply looks in the cache before making recursive calls:

 ```int Fib::fib(int n) { if (n == 0 || n == 1) return 1; if (cache[n] != -1) return cache[n]; cache[n] = fib(n-1) + fib(n-2); return cache[n]; } ```

Step 3: Remove the recursion: Here, when you realize that you only make smaller recursive calls, you can instead build the cache from small to big:

 ```int Fib::fib(int n) { int i; cache[0] = 1; cache[1] = 1; for (i = 2; i <= n; i++) cache[i] = cache[i-1] + cache[i-2]; return cache[n]; } ```

Step 4: Reduce the memory footprint: Since you're only using the last two entries of the cache, you simply hold the last two entries instead of the whole cache. Here's a fun way -- remember what a deque is?

 ```int Fib::fib(int n) { deque cache; if (n == 0 || n == 1) return 1; cache.push_back(1); cache.push_back(1); for (i = 2; i <= n; i++) { cache.push_back(cache[0] + cache[1]); cache.erase(cache.begin()); } return cache[1]; } ```

### Grading

For each part, stating the step was 1.5 points, and showing how it mapped to the Fibonacci implementation was another 1.5 points.

## Question 5: 12 points

Part A: This is not a hard graph to eyeball. Looks pretty easy to max out edges Ct and Dt with:

• sBDt: 76 units of flow.
• sACt: 29 units of flow.
• sADt: 8 units of flow.
That's 113 units of flow.

Part B: The cut is straightforward: Ct and Dt. Remember, the minimum cut is not a number. It is a collection of edges that separates the source and the sink and has a minimum sum of edge weights.

Part C: Yes, this is a pain. Sorry, but you cannot have been surprised. With the greedy DFS, you perform a DFS, visiting edges from largest capacity to smallest. The first augmenting path is therefore sACBDt with a flow of 33. I can guarantee you that drawing the residual by hand is easier than using Open Office, as I do here:

The next path is sBDt with a flow of 51. Here's the residual:

The last path is not the shortest, but it cuts off the source from the sink: sADBCt with a flow of 29. The final residual:

Part D: It's a lot easier to simply use the three paths of length three from our initial eyeballing:

• sBDt: 76 units of flow.
• sACt: 29 units of flow.
• sADt: 8 units of flow.
Those aren't the only paths that you can use. If you start with sADt, your journey to the max flow will be more tedious.

### Grading

• Flow: 2 points
• Cut: 2 points
• Each path: 1 point for path/flow, 1 point for the residual
• Paths for Edmonds-Karp: 2 points