英語中,有些單詞能夠出如今其餘單詞後面。例如「Love」能夠出如今「I」以後,「You」能夠出如今「Love」以後,所以它們能構成「I Love You」這句話。
如今給你一些單詞間的關係,你能計算出最多能有幾個單詞組合在一塊兒構成一句話嗎?java
輸入包含多組數據。
每組數據的第一行包含一個正整數n (1≤n≤10000)。
緊接着n行單詞關係,每行包含兩個單詞A和B,表示單詞B能出如今A後面。單詞長度不超過32個字符,而且只有字母組成。
不存在循環的依賴關係。算法
對應每組數據,輸出最多有幾個單詞能構成一個句子。數組
1 hello world 3 I love love you love me
2 3
假設某一組數據有n條,第i條記錄爲(Ini,Outi),當Outi=Inj時,能夠將(Ini,Outi)、(Inj,Outj)結合成(Ini,Outi,Outj),它們出現的前後表示有向關係,不存在循環的依賴關係。依據這個的思路能夠將全部的(Ini,Outi)記錄構形成一個有向無環圖。Ini、Outi表示圖中的頂點,(Ini,Outi)表示有向邊。
假設有輸入記錄:(A,B)、(B,C)、(C,D)、(B,D)、(E,F)、(F,G)、(C,E)、(I,B)、(A,F)。根據輸入的前後順序構造一個有向圖,有向圖的構造如圖2-1所示。
圖2-1 根據輸入構造有向無環圖
根據輸入的添加過程,能夠知道構造有向無環圖的過程。假設G是有向圖,V是圖G的頂點集合。對於某一個輸入序列(Ini,Outi)。分四種狀況:
若是Ini和Outi都在V中,則在圖G中,添加Ini到Outi的有向邊。
若是隻有Ini在G中,則在圖G中添加新的頂點Outi和有向邊(Ini,Outi),而且將Outi加入到V中。
若是隻有Outi在G中,則在圖G中添加新的頂點In_i和有向邊(Ini,Outi),而且將Ini加入到V中。
若是Ini和Outi都不在V中,則在圖G中添加新的頂點Ini、Outi和有向邊(Ini,Outi),而且將Ini和Outi加入到V中。
對全部的輸入序列(Ini,Outi)都進行上面的操做,最後構形成一個有向無環圖G。圖G可能存在多個連通分支。測試
對有向無環圖G的每個起始頂點進行深度優先遍歷,最長的路徑的頂點數就是所求的單詞個數。圖1中(A,B,C,E,F,G) (I,B,C,E,F,G)和就是最長的路徑,頂點數爲6,因此單詞個數爲6。this
深度優先遍歷很是耗時,因此能夠在步驟一的過程當中記錄以頂點V爲結束點的最長有向線段的頂點數。當圖構建完成後,能夠對圖的全部頂點遍歷一次,找出V對應用的最大值就能夠了。spa
import java.util.*; /** * 解法一會生產超時 * Declaration: All Rights Reserved !!! */ public class Main { /** * 有向圖 */ private static class G { // 頂點集合,經過頂點的名稱來找頂點。 private final Map<String, V> VERTEX = new HashMap<>(); // 有向無環圖的起始頂點,經過頂點的名稱來找起始頂點。 private final Map<String, V> STARTING = new HashMap<>(); } /** * 圖的頂點對象,使用圖的鄰接表表示 */ private static class V { // 頂點的名稱 private String n; // 鄰接點 private final Set<V> ADJ = new HashSet<>(); V(String n) { this.n = n; } } public static void main(String[] args) { // Scanner scanner = new Scanner(System.in); Scanner scanner = new Scanner(Main.class.getClassLoader().getResourceAsStream("data2.txt")); while (scanner.hasNext()) { // 建立一個圖對象 G g = new G(); int n = scanner.nextInt(); for (int i = 0; i < n; i++) { String a = scanner.next(); String b = scanner.next(); addEdge(g, a, b); } System.out.println(getLongestPathLength(g)); } scanner.close(); } /** * 求圖g最長路徑的長度 * TIP: 這是一個很是耗時的方法 * * @param g 圖 * @return 最長路徑的長度 */ private static int getLongestPathLength(G g) { if (g == null || g.VERTEX.isEmpty()) { return 0; } int[] r = {0}; int[] t = {0}; Collection<V> vs = g.STARTING.values(); for (V v : vs) { t[0] = 0; findLongestPathLength(v, t, r); } return r[0]; } /** * 找以v頂點開始的最長路徑的長度 * * @param v 頂點 * @param curr 從最開始到當前處理的頂點的上一個頂點,一個有curr個頂點 * @param result 長度爲1的數組,用於記錄結果,記錄最長路徑的頂點數 */ private static void findLongestPathLength(V v, int[] curr, int[] result) { curr[0]++; if (result[0] < curr[0]) { result[0] = curr[0]; } Collection<V> vs = v.ADJ; // 處理領接點 for (V t : vs) { findLongestPathLength(t, curr, result); } // 現場還原 curr[0]--; } /** * 向圖g中添加邊(a, b); * * @param g 圖 * @param a 邊的起始點 * @param b 邊的終點 */ private static void addEdge(G g, String a, String b) { // 判斷兩個頂點是否都在圖中 boolean ca = g.VERTEX.containsKey(a); boolean cb = g.VERTEX.containsKey(b); // 兩個頂點都已經存在了 if (ca && cb) { // 將b設置爲a的鄰接點 g.VERTEX.get(a).ADJ.add(g.VERTEX.get(b)); } // 頂點a已經存在,b不存在 else if (ca && !cb) { V bv = new V(b); // 將頂點b放到頂點集合中 g.VERTEX.put(b, bv); // 將b設置爲a的鄰接點 g.VERTEX.get(a).ADJ.add(bv); } // 頂點a不存存,b存在 else if (!ca && cb) { V av = new V(a); // 將頂點a放到頂點集合中 g.VERTEX.put(a, av); // 將b設置爲a的鄰接點 av.ADJ.add(g.VERTEX.get(b)); // 若是b起始頂點,加入(a, b)邊以後,b就不是起始頂點了 if (g.STARTING.containsKey(b)) { g.STARTING.remove(b); } // a是新的起始頂點 g.STARTING.put(a, av); } // 兩個頂點都不在圖中 else { // 構造兩個頂點 V av = new V(a); V bv = new V(b); // 將b設置爲a的鄰接點 av.ADJ.add(bv); // 將頂點a、b放到頂點集合中 g.VERTEX.put(a, av); g.VERTEX.put(b, bv); // a爲起始頂點 g.STARTING.put(a, av); } } }
import java.util.*; /** * 解法二 * Declaration: All Rights Reserved !!! */ public class Main2 { public static void main(String[] args) { // Scanner scanner = new Scanner(System.in); Scanner scanner = new Scanner(Main.class.getClassLoader().getResourceAsStream("data2.txt")); while (scanner.hasNext()) { int row = scanner.nextInt(); // 頂點集合,同是記錄頂點爲終點的最長有向線段的頂點數 // key(=String)爲起始頂點,value(=Integer)爲以key結點的最長有向線段的頂點數,當只有一個頂點時value爲1 Map<String, Integer> vertex = new HashMap<>(); // 圖 // 記錄以key(=String)開爲起始頂點的有向邊,value(List<String>)鄰接頂點集合 Map<String, List<String>> graph = new HashMap<>(); for (int i = 0; i < row; i++) { // 輸入兩個單詞,同時也表示兩個頂表示的有向邊 String a = scanner.next(); String b = scanner.next(); // 若是是新的頂點,就加入到頂點集合中 if (!vertex.containsKey(a)) { vertex.put(a, 1); } if (!vertex.containsKey(b)) { vertex.put(b, 1); } // 獲取頂點a的有鄰接頂點集合,若是集合不存就建立 List<String> list = graph.get(a); if (list == null) { list = new ArrayList<>(); graph.put(a, list); } // 添加a的鄰接頂點b list.add(b); visitAll(a, b, vertex, graph); } int max = 0; for (Integer val : vertex.values()) { if (val > max) { max = val; } } System.out.println(max); } } /** * 更新以b爲終點的最長有向線段的頂點數,其中(a, b)表示新添加的有向線段 * * @param a 頂點 * @param b 頂點 * @param vertex 頂點集合 * @param graph 有向圖 */ private static void visitAll(String a, String b, Map<String, Integer> vertex, Map<String, List<String>> graph) { // 以b爲終點的最長線段包含的頂點數 int val = vertex.get(b); // 原先以a爲終點的最長線段包含的頂點數,再加上1,表示從包含(a, b), // 以b爲終點的最長線段包含的頂點數 int t = vertex.get(a) + 1; // 記錄以b爲終點的最長有向線段的頂點數 if (val < t) { vertex.put(b, t); // 接在b後面的頂點都要進行更新 List<String> list = graph.get(b); if (list != null) { for (String s : list) { visitAll(b, s, vertex, graph); } } } // 以b爲終點的最長有向線段的頂點數沒有發生變化,就不須要再進行處理 } }
import java.util.*; /** * 解法三: * 由於解法一(Main)會生產超時,如今對他進行改進 * Declaration: All Rights Reserved !!! */ public class Main3 { /** * 有向圖 */ private static class G { // 頂點集合,經過頂點的名稱來找頂點。 private final Map<String, V> VERTEX = new HashMap<>(); } /** * 圖的頂點對象,使用圖的鄰接表表示 */ private static class V { // 頂點的名稱 private String n; // 以當前頂點爲終點的最長有向線段的頂點數,只有一個頂點時爲1 private int v; // 鄰接點 private final Set<V> ADJ = new HashSet<>(); V(String n, int v) { this.n = n; this.v = v; } } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // Scanner scanner = new Scanner(Main3.class.getClassLoader().getResourceAsStream("data2.txt")); // 建立一個圖對象,能夠重複使用 G g = new G(); while (scanner.hasNext()) { // 清空內容 g.VERTEX.clear(); int n = scanner.nextInt(); for (int i = 0; i < n; i++) { String a = scanner.next(); String b = scanner.next(); addEdge(g, a, b); } System.out.println(getLongestPathLength(g)); } scanner.close(); } /** * 求圖g最長路徑的長度 * * @param g 圖 * @return 最長路徑的長度 */ private static int getLongestPathLength(G g) { if (g == null || g.VERTEX.isEmpty()) { return 0; } int max = 0; Collection<V> vs = g.VERTEX.values(); for (V v : vs) { if (max < v.v) { max = v.v; } } return max; } /** * 向圖g中添加邊(a, b); * * @param g 圖 * @param a 邊的起始點 * @param b 邊的終點 */ private static void addEdge(G g, String a, String b) { // // 判斷兩個頂點是否都在圖中 // boolean ca = g.VERTEX.containsKey(a); // boolean cb = g.VERTEX.containsKey(b); V av = g.VERTEX.get(a); V bv = g.VERTEX.get(b); if (av == null) { av = new V(a, 1); // 將頂點a放到頂點集合中 g.VERTEX.put(a, av); } if (bv == null) { bv = new V(b, 1); // 將頂點b放到頂點集合中 g.VERTEX.put(b, bv); } // 將b設置爲a的鄰接點 g.VERTEX.get(a).ADJ.add(g.VERTEX.get(b)); update(g.VERTEX.get(a), g.VERTEX.get(b), g); } /** * 更新結束頂點的長度記數 * * @param a 頂點 * @param b 頂點 * @param g 圖 */ private static void update(V a, V b, G g) { // 原先以a爲終點的最長線段包含的頂點數,再加上1,表示從包含(a, b), // 以b爲終點的最長線段包含的頂點數 int lenA = a.v + 1; // 以b爲終點的最長線段包含的頂點數 int lenB = b.v; if (lenA > lenB) { b.v = lenA; Set<V> vs = b.ADJ; for (V v : vs) { update(b, v, g); } } } }