// Solution to "Translations" by Bob Roos import java.util.*; import java.io.*; // One node in a graph: class Node { public int self; public int indeg,outdeg; public Node(int i) { self = i; indeg = outdeg = 0; } } // An adjacency list representation of the neighbors of a node: class NodeList { public Vector nbr; public NodeList() { nbr = new Vector(); } } public class Translations { static BufferedReader in; static StringTokenizer tok; static int n; // input: number of phrases in each language static int m; // number of nodes in graphs (for graph isomorphism test) static int caseNum; // input case number static String lang1[][], lang2[][]; // input: lists of 2-word phrases static Vector nodes1,nodes2; // alphabetized lists of words in each language static NodeList[] g1,g2; // the graphs formed by the 2-word phrases static Node[] nl1,nl2; // list of nodes for each graph, including degrees static Vector[] cand; // candidates for matching with each node in g1 static int label[]; // g1 labels for g2 nodes static int match[]; // g2 labels for g1 nodes static int nummatched; // number of matched nodes ///////////////////////////////////////////////////////////////////// // Main: read in the input, set up the problem, and call "iso" to // determine the isomorphism: public static void main(String[] args) throws IOException { caseNum = 0; in = new BufferedReader(new InputStreamReader(System.in)); String line = in.readLine().trim(); while (line != null) { n = Integer.parseInt(line); if (n == 0) { break; } // Read in phrases for first language: nodes1 = new Vector(); lang1 = new String[n][2]; for (int i = 0; i < n; i++) { line = in.readLine().trim(); tok = new StringTokenizer(line); lang1[i][0] = tok.nextToken(); lang1[i][1] = tok.nextToken(); // add words to the sorted word lists: insert(nodes1,lang1[i][0]); insert(nodes1,lang1[i][1]); } // Read in phrases for second language: nodes2 = new Vector(); lang2 = new String[n][2]; for (int i = 0; i < n; i++) { line = in.readLine().trim(); tok = new StringTokenizer(line); lang2[i][0] = tok.nextToken(); lang2[i][1] = tok.nextToken(); // add words to the sorted word lists: insert(nodes2,lang2[i][0]); insert(nodes2,lang2[i][1]); } // Really a debugging test -- this shouldn't happen! if (nodes1.size() != nodes2.size()) { System.out.println("impossible"); continue; } m = nodes1.size(); // Number of nodes in the two graphs // Store as graphs: g1 = new NodeList[m]; g2 = new NodeList[m]; for (int i = 0; i < m; i++) { g1[i] = new NodeList(); g2[i] = new NodeList(); } // Create nodelists for each graph: nl1 = new Node[m]; nl2 = new Node[m]; for (int i = 0; i < m; i++) { nl1[i] = new Node(i); nl2[i] = new Node(i); } // Convert the edge lists into adjacency lists: convert(lang1,nodes1,g1,nl1); convert(lang2,nodes2,g2,nl2); /* DEBUG: System.out.println("First language:"); for (int i = 0; i < n; i++) System.out.println(lang1[i][0]+"/"+lang1[i][1]); for (int i = 0; i < g1.length; i++) { NodeList nl = (NodeList)g1[i]; System.out.print(nodes1.get(i)+": indegree = " + nl1[i].indeg+ ", outdegree = " +nl1[i].outdeg + "\n "); for (int j = 0; j < nl.nbr.size(); j++) { Node nd = (Node)nl.nbr.get(j); System.out.print(nd.self+" "); } System.out.println(); } System.out.println("Second language:"); for (int i = 0; i < n; i++) System.out.println(lang2[i][0]+"/"+lang2[i][1]); System.out.print("word list: "); for (int i = 0; i < nodes2.size(); i++) System.out.print(nodes2.get(i)+" "); System.out.println(); for (int i = 0; i < g2.length; i++) { NodeList nl = (NodeList)g2[i]; System.out.print(nodes2.get(i)+": indegree = " + nl2[i].indeg+ ", outdegree = " +nl2[i].outdeg + "\n "); for (int j = 0; j < nl.nbr.size(); j++) { Node nd = (Node)nl.nbr.get(j); System.out.print(nd.self+" "); } System.out.println(); } END DEBUG */ caseNum++; if (caseNum > 1) System.out.println(); iso(); line = in.readLine().trim(); } } ///////////////////////////////////////////////////////////////////// public static void iso() { // First, determine all possible candidate matches based on indegree // and outdegree: cand = new Vector[m]; for (int i = 0; i < m; i++) { //System.out.print("Candidate matches for " + nodes1.get(i)); cand[i] = new Vector(); Node first = (Node)nl1[i]; for (int j = 0; j < m; j++) { Node second = nl2[j]; if (first.indeg == second.indeg && first.outdeg == second.outdeg) { cand[i].add(new Integer(j)); //System.out.print(" "+nodes2.get(((Integer)cand[i].get(cand[i].size()-1)).intValue())); } } //System.out.println(); } // Initialize matchups: label = new int[m]; match = new int[m]; for (int i = 0; i < m; i++) { label[i] = -1; match[i] = -1; } // Now recursively backtrack search for an isomorphism: nummatched = 0; // Try all candidates for node 0 (until successful isomorphism): for (int k = 0; k < cand[0].size(); k++) { int j = ((Integer)cand[0].get(k)).intValue(); if (recursiveMatch(0,j)) break; } for (int i = 0; i < m; i++) { if (match[i] >= 0) // THIS TEST FOR DEBUGGING ONLY -- ISO SHOULD ALWAYS EXIST System.out.println(nodes1.get(i)+"/"+nodes2.get(match[i])); else System.out.println(nodes1.get(i)+"/"+(-1)); } } ///////////////////////////////////////////////////////////////////// // Insert unique word into sorted list; these lists will be used // for converting words into indices and for determining the output // order of the solution. public static void insert(Vector nodes, String word) { for (int i = 0; i < nodes.size(); i++) { String temp = (String)nodes.get(i); if (word.equals(temp)) // already there -- do nothing return; if (word.compareTo(temp) < 0) { // found its position -- insert it nodes.insertElementAt(word,i); return; } } nodes.add(word); // found its position -- insert it } ///////////////////////////////////////////////////////////////////// // Convert an array of edges, lang, into an array of adjacency lists, g. // ("nodes" and "nl" are useful auxiliary arrays for looking up words // and maintaining degree information). public static void convert(String lang[][], Vector nodes, NodeList[] g, Node[] nl) { for (int i = 0; i < n; i++) { String start = lang[i][0]; String end = lang[i][1]; int startpos = nodes.indexOf(start); int endpos = nodes.indexOf(end); nl[startpos].outdeg++; nl[endpos].indeg++; g[startpos].nbr.add(nl[endpos]); } } ///////////////////////////////////////////////////////////////////// public static boolean recursiveMatch(int i, int j) { // See if the j has already been matched to something else: if (label[j] >= 0 && label[j] != i) { //System.out.println("Can't match " + nodes1.get(i) +" to "+nodes2.get(j) // +"; "+nodes2.get(j)+" is already matched to " + nodes1.get(label[j])); return false; } // It hasn't; see if the matched children of node i agree with // the matched children of node j: for (int k = 0; k < g1[i].nbr.size(); k++) { Node nd = (Node)g1[i].nbr.get(k); if (match[nd.self] >= 0) { int l = match[nd.self]; boolean found = false; //System.out.println("Searching for " + nodes2.get(temp)); for (int m2 = 0; m2 < g2[j].nbr.size(); m2++) { Node nd2 = (Node)g2[j].nbr.get(m2); if (l == nd2.self) { found = true; break; } } if (!found) { return false; } } } // See if the matched children of node j agree with // the matched children of node i: for (int k = 0; k < g2[j].nbr.size(); k++) { Node nd = (Node)g2[j].nbr.get(k); if (label[nd.self] >= 0) { int l = label[nd.self]; boolean found = false; //System.out.println("Searching for " + nodes2.get(temp)); for (int m2 = 0; m2 < g1[i].nbr.size(); m2++) { Node nd2 = (Node)g1[i].nbr.get(m2); if (l == nd2.self) { found = true; break; } } if (!found) { return false; } } } // Hmm... that's not good enough. So let's also check "back edges". // I regret having chosen an adjacency list representation here! for (int k = 0; k < m; k++) { if (k == i) continue; if (match[k] >= 0) { // see if i is a child of k; if so, see if j is a child of match[k]: for (int l = 0; l < g1[k].nbr.size(); l++) { Node nd = (Node)g1[k].nbr.get(l); if (nd.self == i) { boolean found = false; for (int m2 = 0; m2 < g2[match[k]].nbr.size(); m2++) { Node nd2 = (Node)g2[match[k]].nbr.get(m2); if (nd2.self == j) { found = true; break; } } if (!found) return false; } } } } // They agree; do the matchup: label[j] = i; match[i] = j; nummatched++; //System.out.println("Trying to match " + nodes1.get(i)+" to " + nodes2.get(j)); // If this is the last node to be matched, verify the graph isomorphism: if (nummatched == m) { //System.out.println("Testing iso:"); boolean invalid = false; for (int k = 0; k < m; k++) { int l = match[k]; //System.out.println("Comparing "+nodes1.get(k)+" and "+nodes2.get(l)); // see if neighborlist of k in g1 matches neighbor list of l in g2: for (int m3 = 0; m3 < g1[k].nbr.size(); m3++) { Node nd = (Node)g1[k].nbr.get(m3); int ndname = nd.self; //System.out.println("Child " + nodes1.get(ndname)); boolean found = false; int temp = match[ndname]; //System.out.println("Searching for " + nodes2.get(temp)); for (int m2 = 0; m2 < g2[l].nbr.size(); m2++) { Node nd2 = (Node)g2[l].nbr.get(m2); if (temp == nd2.self) { found = true; break; } } if (!found) { invalid = true; break; } } if (invalid) break; } if (invalid) { label[j] = -1; match[i] = -1; nummatched--; return false; } return true; } // Match one more node: for (int k = 0; k < cand[i+1].size(); k++) { int m = ((Integer)cand[i+1].get(k)).intValue(); if (recursiveMatch(i+1,m)) return true; } label[j] = -1; match[i] = -1; nummatched--; return false; } ///////////////////////////////////////////////////////////////////// /* // Insert Node into sorted list. public static void insertNode(Vector nodes, Node nd, int id) { for (int i = 0; i < nodes.size(); i++) { Node temp = (Node)nodes.get(i); if (id == temp.self) // already there -- do nothing return; if (id < temp.self) { // found its position -- insert it nodes.insertElementAt(nd,i); return; } } nodes.add(word); // found its position -- insert it } */ }