【编程马拉松】【013-最长句子】

【编程马拉松算法目录>>>】


【013-最长句子】【工程下载>>>】


1 题目描述


  英语中,有些单词可以出现在其他单词后面。例如“Love”可以出现在“I”之后,“You”可以出现在“Love”之后,因此它们能构成“I Love You”这句话。
  现在给你一些单词间的关系,你能计算出最多能有几个单词组合在一起构成一句话吗?

1.1 输入描述:


  输入包含多组数据。
  每组数据的第一行包含一个正整数n (1≤n≤10000)。
  紧接着n行单词关系,每行包含两个单词A和B,表示单词B能出现在A后面。单词长度不超过32个字符,并且只有字母组成。
  不存在循环的依赖关系。

1.2 输出描述:


  对应每组数据,输出最多有几个单词能构成一个句子。

1.3 输入例子:


1
hello world
3
I love
love you
love me

1.4 输出例子:


2
3

2 解题思路


2.1 步骤一


  假设某一组数据有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可能存在多个连通分支。

2.2 步骤二


  对有向无环图G的每一个起始顶点进行深度优先遍历,最长的路径的顶点数就是所求的单词个数。图1中(A,B,C,E,F,G) (I,B,C,E,F,G)和就是最长的路径,顶点数为6,所以单词个数为6。

2.3 改进


  深度优先遍历非常耗时,所以可以在步骤一的过程中记录以顶点V为结束点的最长有向线段的顶点数。当图构建完成后,可以对图的所有顶点遍历一次,找出V对应用的最大值就可以了。

3 算法实现


3.1 方法一


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);
        }
    }
}

3.1 方法二


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为终点的最长有向线段的顶点数没有发生变化,就不需要再进行处理
    }
}

3.1 方法三


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);
            }
        }
    }
}

4 测试结果


这里写图片描述

5 其它信息


因为markddow不好编辑,因此将文档的图片上传以供阅读。Pdf和Word文档可以在Github上进行【下载>>>】

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
马拉松编程题目是一种挑战性的编程竞赛,要求参赛者在有限的时间内解决一系列复杂的编程问题。这些题目旨在测试参赛者的编程技能、算法思维和解决问题的能力。 在NOI(全国青少年信息学奥林匹克竞赛)中,编程马拉松题目非常有名。参赛者通常需要在一天内完成多个编程问题,每个问题的难度和复杂性都不同。这些题目可能涉及算法数据结构、动态规划、图论等各种领域。 参赛者在比赛开始时会收到一份题目清单,然后他们需要逐个解决各个问题。解题速度和准确性是评判参赛者成绩的重要指标。因为时间有限,参赛者需要在压力下迅速找到问题的解决方法,并编写高效的代码。 编程马拉松不仅考察了参赛者的编程技能,也考验了他们的团队合作能力。许多编程马拉松都是以小组形式进行,每个小组的成员需要相互配合,合理分工,共同完成任务。 参加编程马拉松有助于提升参赛者的编程能力和解决问题的能力。通过面临各种难题和时间限制,参赛者能够锻炼自己的思维能力和解决问题的方法。此外,参加编程马拉松还能与其他优秀的编程爱好者交流、学习,扩展自己的知识和视野。 总之,编程马拉松是一种激动人心的编程竞赛,可以帮助参赛者提升编程能力和解决问题的能力。这种比赛不仅考察了编程技能,还培养了团队合作和应对压力的能力,对于对编程有兴趣的人来说是一次难得的学习和锻炼机会。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值