int Trekking::findCamps(string trail, vector <string> 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; } |
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!
My grading was:
"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.
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 <int> 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]; } |
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: