英语中,有些单词可以出现在其他单词后面。例如“Love”可以出现在“I”之后,“You”可以出现在“Love”之后,因此它们能构成“I Love You”这句话。
现在给你一些单词间的关系,你能计算出最多能有几个单词组合在一起构成一句话吗?
输入包含多组数据。
每组数据的第一行包含一个正整数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。
深度优先遍历非常耗时,所以可以在步骤一的过程中记录以顶点V为结束点的最长有向线段的顶点数。当图构建完成后,可以对图的所有顶点遍历一次,找出V对应用的最大值就可以了。
import java.util.*;
/** * 解法一会生产超时 * Author: 王俊超 * Time: 2016-05-10 10:58 * CSDN: http://blog.csdn.net/derrantcm * Github: https://github.com/Wang-Jun-Chao * 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.*;
/** * 解法二 * Author: 王俊超 * Time: 2016-05-10 22:01 * CSDN: http://blog.csdn.net/derrantcm * Github: https://github.com/Wang-Jun-Chao * 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)会生产超时,现在对他进行改进 * Author: 王俊超 * Time: 2016-05-10 10:58 * CSDN: http://blog.csdn.net/derrantcm * Github: https://github.com/Wang-Jun-Chao * 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);
}
}
}
}
因为markddow不好编辑,因此将文档的图片上传以供阅读。Pdf和Word文档可以在Github上进行【下载>>>】。