【编程马拉松】【019-一笔画】

【编程马拉松算法目录】


【019-一笔画】【工程下载>>>】


1 题目描述



  咱们来玩一笔画游戏吧,规则是这样的:有一个连通的图,能否找到一个恰好包含了所有的边,并且没有重复的路径。

1.1 输入描述:



  输入包含多组数据。每组数据的第一行包含两个整数n和m (2≤n, m≤1000),其中n是顶点的个数,m是边的条数。紧接着有m行,每行包含两个整数from和to (1 ≤ from, to ≤ n, from != to),分别代表边的两端顶点。边是双向的,并且两个顶点之间可能不止一条边。

1.2 输出描述:



  对应每一组输入,如果能一笔画则输出“Yes”;否则输出“No”。

1.3 输入例子:


3 3
1 2
2 3
1 3
4 7
1 2
2 1
1 3
1 4
1 4
2 3
4 3

1.4 输出例子:


Yes
No

2 解题思路



  题目要求一个连通的有向图是否可以一笔画完。这是一个可行遍性问题,即从图中一个顶点出发不重复地遍历完所有的边并回到起始顶点,这种回路是欧拉回路。在解答该问题前先对欧拉回路相关的内容进行介绍。


2.1 欧拉回路


2.1.1 欧拉通路、欧拉回路、欧拉图



  无向图:

  1) 设G 是连通无向图,则称经过G 的每条边一次并且仅一次的路径为欧拉通路;

  2) 如果欧拉通路是回路(起点和终点是同一个顶点),则称此回路为欧拉回路(Euler circuit);

  3) 具有欧拉回路的无向图G 称为欧拉图(Euler graph)。

有向图:

  1) 设D是有向图,D的基图连通,则称经过D的每条边一次并且仅一次的有向路径为有向欧拉通路;

  2) 如果有向欧拉通路是有向回路,则称此有向回路为有向欧拉回路(directed Euler circuit);

  3) 具有有向欧拉回路的有向图D称为有向欧拉图(directed Euler graph)。

  图1是有向图。

图1 有向图和无向图

2.1.2 定理及推论



  欧拉通路和欧拉回路的判定是很简单的,请看下面的定理及推论。

  定理2.1 无向图G存在欧拉通路的充要条件是:

  G为连通图,并且G仅有两个奇度结点(度数为奇数的顶点)或者无奇度结点。

  推论2.1

  1) 当G是仅有两个奇度结点的连通图时,G的欧拉通路必以此两个结点为端点。

  2) 当G是无奇度结点的连通图时,G必有欧拉回路。

  3) G为欧拉图(存在欧拉回路)的充分必要条件是G为无奇度结点的连通图。

  例如图1(a)所示的无向图,存在两个奇度顶点v2和v5,所以存在欧拉通路,且欧拉通路必以这两个顶点为起始顶点和终止顶点;该无向图不存在欧拉回路。图2-1(b)所示的无向图为欧拉图。

  定理2.2 有向图D存在欧拉通路的充要条件是:

  D为有向图,D的基图连通,并且所有顶点的出度与入度都相等;或者除两个顶点外,其余顶点的出度与入度都相等,而这两个顶点中一个顶点的出度与入度之差为1,另一个顶点的出度与入度之差为-1。

  推论2.2

  1) 当D除出、入度之差为1,-1的两个顶点之外,其余顶点的出度与入度都相等时,D的有向欧拉通路必以出、入度之差为1的顶点作为始点,以出、入度之差为-1的顶点作为终点。

  2) 当D的所有顶点的出、入度都相等时,D中存在有向欧拉回路。

  3) 有向图D为有向欧拉图的充分必要条件是D的基图为连通图,并且所有顶点的出、入度都相等。

  例如图1(c)所示的有向图,顶点v2和v4入度和出度均为1;顶点v1的出度为2、入度为1,二者差值为1;顶点v3的出度为1、入度为2,二者相差为-1;所以该有向图只存在有向欧拉通路,且必须以顶点v1为始点,以顶点v3为终点。图1(d)所示的有向图不存在有向欧拉通路。

2.2 解题步骤



  首先根据输入构造图的邻接矩阵,通过邻接矩阵判断图是否连通,不连通说明不可以一笔画完,如果连通,再判断图是否有奇度顶点,有就不能一笔画完,没有就说明可以一笔画完。

3 算法实现


import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * Author: 王俊超
 * Time: 2016-05-12 09:04
 * CSDN: http://blog.csdn.net/derrantcm
 * Github: https://github.com/Wang-Jun-Chao
 * Declaration: All Rights Reserved !!!
 */
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
//        Scanner scanner = new Scanner(Main.class.getClassLoader().getResourceAsStream("data3.txt"));
        while (scanner.hasNext()) {
            int n = scanner.nextInt();
            int m = scanner.nextInt();

            // 记录边
            int[] edge = new int[m * 2];

            for (int i = 0; i < edge.length; i++) {
                edge[i] = scanner.nextInt();
            }

            if (draw(n, edge)) {
                System.out.println("Yes");
            } else {
                System.out.println("No");
            }

        }

        scanner.close();
    }

    /**
     * 图是否可以笔画完(判断无向图是否存在欧拉通路)
     *
     * @param n    顶点点个数,顶点的编号从1到n
     * @param edge 边的连接数组,两个一起表示一条边
     * @return true:可以一笔画完,false:不可以一笔画完
     */
    private static boolean draw(int n, int[] edge) {

        int[] vertex = new int[n + 1];

        // 统计每个顶点的度数
        for (int i : edge) {
            vertex[i]++;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////
        // 无向图G存在欧拉通路的充要条件是:G为连通图,并且G仅有两个奇度结点(度数为奇数的顶点)或者无奇度结点。
        ///////////////////////////////////////////////////////////////////////////////////////////

        // 统计奇度顶点个数
        int count = 0;
        for (int i = 1; i < vertex.length; i++) {
            if (vertex[i] % 2 != 0) {
                count++;
            }
        }

        // 奇度顶点不为0且不为2说明不存在欧拉通路
        if (count != 0 && count != 2) {
            return false;
        }

        // 构造边的邻接矩阵
        int[][] graph = new int[n + 1][n + 1];

        for (int i = 0; i < edge.length; i += 2) {
            int v = edge[i];
            int w = edge[i + 1];
            graph[v][w]++;
            graph[w][v]++;
        }

        // 清空顶号入度标记,将它作为访问标记使用,0表示没有访问过,1表示访问过
        for (int i = 0; i < vertex.length; i++) {
            vertex[0] = 0;
        }

        List<Integer> list = new ArrayList<>(n);

        // 有向图连通,那么从任意一个顶点都可以访问到其它的顶点
        // 从第一个顶点开始访问,进行广度优先遍历
        vertex[1] = 1;
        list.add(1);
        while (!list.isEmpty()) {
            int v = list.remove(0);
            for (int i = 1; i <= n; i++) {
                // 边(v, i),t为0说明v不能直接到i
                int t = graph[v][i];
                // 如果(v, i)可达,且顶点i没有被访问过,就标记已经访问过,添加到访问队列中
                if (t != 0 && vertex[i] == 0) {
                    vertex[i] = 1;
                    list.add(i);
                }
            }
        }

        for (int i = 1; i < vertex.length; i++) {
            // 还有顶点没有访问到,说明图不连通
            if (vertex[i] == 0) {
                return false;
            }
        }

        return true;
    }
}

4 测试结果



5 其它信息



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

时间: 2024-08-27 01:57:13

【编程马拉松】【019-一笔画】的相关文章

hdu 4542 数论 + 约数个数相关 腾讯编程马拉松复赛

题目:http://acm.hdu.edu.cn/showproblem.php?pid=4542 小明系列故事--未知剩余系 Time Limit: 500/200 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) Total Submission(s): 889    Accepted Submission(s): 207 Problem Description "今有物不知其数,三三数之有二,五五数之有三,七七数之有

HDU 4508 湫湫系列故事——减肥记I (2013腾讯编程马拉松初赛第一场)

http://acm.hdu.edu.cn/showproblem.php?pid=4508 题目大意: 给定一些数据. 每组数据以一个整数n开始,表示每天的食物清单有n种食物. 接下来n行,每行两个整数a和b,其中a表示这种食物可以带给湫湫的幸福值(数值越大,越幸福),b表示湫湫吃这种食物会吸收的卡路里量. 最后是一个整数m,表示湫湫一天吸收的卡路里不能超过m. 思路: 完全背包. 一开始以为是01背包. 敲了01后样例2不对啊!!! 然后改成完全就过了..就改循环体就好了.. #includ

【编程马拉松】【027-最短编辑距离】

[编程马拉松算法目录] [027-最短编辑距离][工程下载>>>] 1 题目描述 1.1 输入描述: UNIX系统下有一个行编辑器ed,它每次只对一行文本做删除一个字符.插入一个字符或替换一个字符三种操作.例如某一行的内容是"ABC",经过把第二个字符替换成"D".删除第一个字符.末尾插入一个字符"B",这三步操作后,内容就变成了"DCB".即"ABC"变成"DCB"需

【编程马拉松】【024-放苹果】

[编程马拉松算法目录] [024-放苹果][工程下载>>>] 1 题目描述 把 M 个同样的苹果放在 N 个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法? 注意:5.1.1 和 1.5.1 是同一种分法,即顺序无关. 1.1 输入描述: 输入包含多组数据. 每组数据包含两个正整数 m和n(1≤m, n≤20). 1.2 输出描述: 对应每组数据,输出一个整数k,表示有k种不同的分法. 1.3 输入例子: 7 3 1.4 输出例子: 8 2 解题思路 2.1 解法一 放苹果,

【编程马拉松】【021-数据库连接池】

[编程马拉松算法目录] [021-数据库连接池][工程下载>>>] 1 题目描述 Web系统通常会频繁地访问数据库,如果每次访问都创建新连接,性能会很差.为了提高性能,架构师决定复用已经创建的连接.当收到请求,并且连接池中没有剩余可用的连接时,系统会创建一个新连接,当请求处理完成时该连接会被放入连接池中,供后续请求使用. 现在提供你处理请求的日志,请你分析一下连接池最多需要创建多少个连接. 1.1 输入描述: 输入包含多组数据,每组数据第一行包含一个正整数n(1≤n≤1000),表示请求

【编程马拉松】【009-数根】

[编程马拉松算法目录>>>] [009-数根][工程下载>>>] 1 题目描述 数根可以通过把一个数的各个位上的数字加起来得到.如果得到的数是一位数,那么这个数就是数根:如果结果是两位数或者包括更多位的数字,那么再把这些数字加起来.如此进行下去,直到得到是一位数为止. 比如,对于24来说,把2和4相加得到6,由于6是一位数,因此6是24的数根.再比如39,把3和9加起来得到12,由于12不是一位数,因此还得把1和2加起来,最后得到3,这是一个一位数,因此3是39的数根.

【编程马拉松】【017-Emacs计算器】

[编程马拉松算法目录] [017-Emacs计算器][工程下载>>>] 1 题目描述 Emacs号称神的编辑器,它自带了一个计算器.与其他计算器不同,它是基于后缀表达式的,即运算符在操作数的后面.例如"2 3 +"等价于中缀表达式的"2 + 3". 请你实现一个后缀表达式的计算器. 1.1 输入描述: 输入包含多组数据. 每组数据包括两行:第一行是一个正整数n (3≤n≤50):紧接着第二行包含n个由数值和运算符组成的列表. "+-*/&

【编程马拉松】【016-过年回家】

[编程马拉松算法目录] [016-过年回家][工程下载>>>] 1 题目描述 NowCoder今年买了一辆新车,他决定自己开车回家过年.回家过程中要经过n个大小收费站,每个收费站的费用不同, 你能帮他计算一下最少需要给多少过路费吗? 1.1 输入描述: 输入包含多组数据,每组数据第一行包含两个正整数m(1≤m≤500)和n(1≤n≤30),其中n表示有n个收费站, 编号依次为1.2.-.n.出发地的编号为0,终点的编号为n,即需要从0到n. 紧接着m行,每行包含三个整数f.t.c,(0≤

【编程马拉松】【018-不喜欢括号】

[编程马拉松算法目录] [018-不喜欢括号][工程下载>>>] 1 题目描述 NowCoder从小就喜欢数学,喜欢在笔记里记录很多表达式.它觉得现在的表达式写法很麻烦,为了提高运算符优先级,不得不添加很多括号,不小心漏了一个右括号的话差之毫厘谬之千里. 因此他改用前缀表达式,例如(2 + 3) * 4写成* + 2 3 4,这样就能避免使用括号了.这样的表达式书写简单,但计算却不够直观.请你写一个程序帮他计算这些前缀表达式吧. 1.1 输入描述: 输入包含多组数据,每组数据包含两行.