// Java solution for problem D // Author: Grzegorz Guspiel import java.io.*; import java.util.*; public class d { boolean debug = false; void out(String s) { System.out.print(s); } final int INF = 1100100100; int[][] lines; int nStops; int beginStop, endStop; String beginStopName, endStopName; ArrayList> g; int[] distance; int[] maxStops; void addEdge(int a, int b) { g.get(a).add(b); g.get(b).add(a); } int lineVertex(int i) { return nStops + i; } int stopVertex(int i) { return i; } void bfs(int source) { distance = new int[g.size()]; for (int i = 0; i < distance.length; i++) distance[i] = INF; ArrayDeque queue = new ArrayDeque(); queue.add(source); distance[source] = 0; while (queue.size() > 0) { int a = queue.pollFirst(); for (int b : g.get(a)) if (distance[b] == INF) { distance[b] = distance[a] + 1; queue.add(b); } } } void solveCase() { g = new ArrayList>(); for (int i = 0; i < nStops + lines.length; i++) g.add(new ArrayList()); for (int line = 0; line < lines.length; line++) for (int stop : lines[line]) { addEdge(stopVertex(stop), lineVertex(line)); } bfs(stopVertex(endStop)); Integer[] lineOrder = new Integer[lines.length]; for (int i = 0; i < lines.length; i++) lineOrder[i] = i; Arrays.sort(lineOrder, new Comparator() { @Override public int compare(Integer a, Integer b) { return distance[lineVertex(a)] - distance[lineVertex(b)]; } }); maxStops = new int[nStops]; for (int line : lineOrder) { int smallestDistance = INF; for (int i = 0; i < lines[line].length; i++) { smallestDistance = Math.min(smallestDistance, distance[stopVertex(lines[line][i])]); } for (int i = 0; i < lines[line].length; i++) { if (distance[stopVertex(lines[line][i])] > smallestDistance + 2) { throw new RuntimeException(); } } int best = -INF; for (int i = 0; i < lines[line].length; i++) { int v = stopVertex(lines[line][i]); if (distance[v] == smallestDistance) { best = Math.max(best, maxStops[v]); } else { maxStops[v] = Math.max(maxStops[v], best); } best++; } best = -INF; for (int i = lines[line].length - 1; i >= 0; i--) { int v = stopVertex(lines[line][i]); if (distance[v] == smallestDistance) { best = Math.max(best, maxStops[v]); } else { maxStops[v] = Math.max(maxStops[v], best); } best++; } } int resultLines = distance[stopVertex(beginStop)] / 2; int resultStops = maxStops[stopVertex(beginStop)]; out("optimal travel from " + beginStopName + " to " + endStopName + ": " + resultLines + " "); if (resultLines == 1) out("line"); else out("lines"); out(", " + resultStops + " "); if (resultStops == 1) out("minute"); else out("minutes"); out("\n"); } int[] parseLine(Map stopNameId, String s) { int i = 0; while (s.charAt(i) != ':') i++; i += 2; ArrayList r = new ArrayList(); StringBuilder current = new StringBuilder(); while (i <= s.length()) { if (i == s.length() || s.charAt(i) == ',') { String word = current.toString(); current.setLength(0); Integer found = stopNameId.get(word); if (found == null) { found = stopNameId.size(); stopNameId.put(word, found); } r.add(found); i++; i++; } else { current.append(s.charAt(i++)); } } int[] a = new int[r.size()]; for (int j = 0; j < r.size(); j++) a[j] = r.get(j); return a; } void solve() throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); int z = Integer.parseInt(reader.readLine()); for (int zz = 0; zz < z; zz++) { reader.readLine(); Map stopNameId = new HashMap(); nStops = parseLine(stopNameId, reader.readLine()).length; int nLines = parseLine(stopNameId, reader.readLine()).length; lines = new int[nLines][]; for (int line = 0; line < nLines; line++) { lines[line] = parseLine(stopNameId, reader.readLine()); } beginStopName = reader.readLine().split(" at ")[1]; endStopName = reader.readLine().split(" at ")[1]; beginStop = stopNameId.get(beginStopName); endStop = stopNameId.get(endStopName); solveCase(); } } public static void main(String[] args) throws Exception { new d().solve(); } } // class h