CS302 Final Exam  Answers and Grading
James S. Plank  May 5, 2017
Question 1
 A: There are only two paths through the graph with flow. The one with the
fewest number of edges is [ A → D → E → F] with a flow of 9.
 B: The changed edges are in red:
 C: [ A → B → C → F], with a flow of 3, and
[ A → B → E → F], with a flow of 7. The original graph is
pictured below.
 D: You'll note that the answer to part B above disconnects the source and the
sink. So we're done. The total flow is 3+7+9 = 19.
 E: Use the answer to B as your residual graph. Then find the nodes connected
to A. These are A, B, D and E. Now, go back to the original graph (from part C)
and find all of the edges from A, B, D or E to C or F. Those are the edges BC, and EF.
Frankly, you don't need to go through all that work  you can pretty much eyeball the graph
and pick out those edges.
Grading
 Part A: 4 points. 2 points for ABDEF5.
 Part B: 4 points.
 Part C: 6 points  three for each answer.
 Part D: 3 points
 Part E: 3 points: 1.5 for BC, 1.5 for EF and .5 for any incorrect edge.
Question 2
Part A: You process node 0 in the first iteration. That will assign shortest
paths to nodes 2 (length=6), 3 (length=34), 4 (length=15) and (length=4). It will
also subtract one from Nincident for each of those. At that point, node 5
will have zero incident edges, so you'll add it to the queue. The answer is:
Q = { 5 }  I was ok with { 0, 5} too.
Nincident = { 0, 2, 1, 3, 1, 0, 2 }
Sh_Path_Length = { 0, sent, 6, 34, 15, 4, sent }
Grading: 2 points for Q.
For Nincident, each of the entries from 1 through 6 was worth 0.5 points.
Same for Sh_Path_Length.
Part B: You process node 1 in the second iteration, and it has a path length of two to
node zero. You'll remove it from the queue. Now, you'll process:
 Node 5 with an edge of 1. The path length is 3, so you'll replace [4,5] with [3,5] in the Q,
and Sh_Path_Length[5] to 3.
 Node 6 with an edge of 2. The path length is 4, so you'll replace [6,6] with [4,6] in the Q,
and Sh_Path_Length[6] to 3.
 Node 2 with an edge of 10. The path length is 12, so you'll add [12,2] Q,
and change Sh_Path_Length[2] to 12.
 Node 3 with an edge of 6. The path length is 8, which doesn't improve 3's shortest path
to zero, so you do nothing.
At the end, then you have:
Q = { [3,5][4,6][7,3][12,2] }
or Q = { [2,1][3,5][4,5][4,6][6,6][7,3][12,2] } if you don't delete nodes from Q.
Sh_Path_Length = { 0, 2, 12, 7, sent, 3, 4 }
Grading: Q: 0.75 each for [3,5], [4,6], [7,3] and [12,2].
I didn't take off for other wrong entries, because you typically lost points for them in Sh_Path_Length.
For Sh_Path_Length, you received .5 for each of 1 through 6 that you got right.
Part C: You process node 1 in the second iteration, and it has a path length of one to
node zero. Node 1 has edges to nodes 1, 2, 3, 5, 6 and 7. Of those, nodes 3 and 7 are not
already on the queue, so you add them to the queue. Their shoretest path lengths are two.
So,
Q = { 2, 5, 6, 3, 7 }  I'm ok if you left 1 on there too.
Sh_Path_Length = { 0, 1, 1, 2, 1, 1, 1, 2, 1, 1 }
Grading: Q: 3 points for { 2, 5, 6, 3, 7 } or { 1, 2, 5, 6, 3, 7 }.
I gave one point of partial credit if you instead added 1, 2, 3, 5, 6 and 7.
For Sh_Path_Length, each element was worth 0.3 points.
Question 3
 A: DFS: O(v+e).
 B: Dijkstra: O(e log(v)).
 C: Both Prim and Kruskal run in O(e log(v)).
 D: DFS: O(v+e).
 E: Modified Dijkstra: O(e log(v)) (see Network Flow Lecture 3).
 F: Dijkstra: O(e log(v)).
 G: BFS: O(v+e)).
 H: Topological sort has the best bigO time: O(n+e)).
 I: DFS: O(v+e)) (See "Using the Residual Graph to find the Minimum Cut" in Network Flow Lecture 1).
 J: Since there is an edge from every node to every other node, there is a path composed of every ordering of nodes. That
is the number of permutations of the nodes: O(n!)
Grading: 2 points per part.
I'm going to provide some commentary here: If a problem specifies v and e, then why would you use n?
While O(n) is linear, and O(v+e) is also linear, the two are different, and if I specify that a graph has
v nodes and e edges, you should give me an answer in terms of v and e. I took off for answers
that only had n in them.
Question 4
Problem 1: This is a shortest path problem on a directed, weighted graph. The
teleport stations are the nodes, and there is an edge from node x to node y
if there is a teleport from station x to station y. The weight of the edge
is the time that it takes to teleport from x to y. The shortest path from
A to B will be the shortest time that it takes to get from A to B.
You use Dijkstra's algorithm to solve this.
Problem 2: This is a BFS problem. The nodes and edges are the same as in the previous
problem, but the graph is unweighted. If a path from A to B has length l,
then you'll wait 20*(l+1) minutes. Therefore the shortest path in this graph will give
you the shortest wait time. You solve this with breadthfirst search.
Problem 3: This is a minimum spanning tree problem.
The nodes are the stations, and the edges are teleports.
Because teleports are bidirectional with the same cost/speed,
the graph is undirected and weighted. The weights are the maintenance costs. You want to
find the minimum spanning tree of this graph, and that will correspond to the teleports that
you don't perform maintenance upon. You perform maintenance on the other teleports.
Grading
A comment before grading. A lot of you answered "Edmonds Karp" for problem B, presumably
meaning that as a shorthand for BFS. EdmondsKarp refers to solving network flow by using
a BFS to find augmenting paths. It is not shorthand for BFS.
 Problem A: Directed graph: 1
 Problem A: Weighted graph: 1
 Problem A: Stations are nodes: 1
 Problem A: Teleports are edges: 1
 Problem A: Weight is time on teleport: .5
 Problem A: Solved with Dijkstra: 3
 Problem B: Directed graph: 1
 Problem B: Unweighted graph: 1
 Problem B: Same nodes and edges as A: 1
 Problem B: Solved with BFS: 3
 Problem C: Undirected graph: 1
 Problem C: Weighted graph: 1
 Problem B: Same nodes and edges: 1
 Problem C: Weight is maintenance cost: .5
 Problem C: Solved with Minimum Spanning Tree: 3
Question 5
There are two ways to do this, and I was hoping that by specifying the problem as I did,
that you would get the correct way:
 Do the recursion on Y: If X equals Y, then you return 1. If
X and Y are the same size, and X doesn't equal Y, then
return 0. Otherwise, do two recursions:
 Append an 'A' to Y and call Reachable() on the same X and
the new Y.
 Append an 'B' to Y, reverse it, and call Reachable() on the same X and the new Y.
This is not the best way to do this, because if the difference in X and Y's
size is n, this can result in roughly 2^{n} calls. Some of those
may be duplicates, so you can help it with memoization, but it will be roughly an exponential
blowup.
 Do the recursion on X: If X equals Y, then you return 1. If
X and Y are the same size, and X doesn't equal Y, then
return 0. Otherwise, do two recursions:
 If X ends with an 'A', then delete that 'A' and call Reachable() on that.
 If X begins with an 'B', then delete that 'B', reverse the string,
and call Reachable() on that.
You'll note that this results in far fewer recursive calls, because you only
make recursive calls when X ends with 'A' or X begins with 'B'.
Here's code for #2 with a main  in
q5.cpp. This only memoizes the 0 answers, because the 1 answers
return instantly.
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <map>
using namespace std;
class Conversion {
public:
string Y;
int Reachable(string X);
map <string, int> Cache;
};
int Conversion::Reachable(string X)
{
string rev;
int i;
if (X == Y) return 1;
if (X.size() <= Y.size()) return 0;
if (Cache.find(X) != Cache.end()) return Cache[X];
if (X[X.size()1] == 'A') {
if (Reachable(X.substr(0,X.size()1))) return 1;
}
if (X[0] == 'B') {
for (i = X.size()1; i > 0; i) rev.push_back(X[i]);
if (Reachable(rev)) return 1;
}
Cache[X] = 0;
return 0;
}
int main(int argc, char **argv)
{
Conversion c;
int ans;
if (argc != 3) { fprintf(stderr, "usage: a.out X Y\n"); exit(1); }
c.Y = argv[2];
ans = c.Reachable(argv[1]);
printf("%s\n", (ans) ? "Yes" : "No");
}

Grading
 Description of the base case when X == Y: 2 points
 Description of the base case when X.size() <= Y.size() and X != Y: 2 points
 Description of solution #1: 2 points. Description of solution #2: 5 points.
 Having the first base case in the code: 1.5 points
 Having the second base case in the code: 1.5 points
 Doing a recursion on the letter 'A' (Either solution 1 or 2): 2 points
 Doing a recursion on the letter 'B' (Either solution 1 or 2): 2 points
 Reversing the string properly in the recursion on 'B': 1 point
 Doing a proper memoization: 3 points