英语中,有些单词能够出如今其余单词后面。例如“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); } } } }