Time limit: 3.000 seconds
限时3.000秒
Background
背景
Graph algorithms form a very important part of computer science and have a lineage that goes back at least to Euler and the famous Seven Bridges of Königsberg problem. Many optimization problems involve determining efficient methods for reasoning about graphs.
计算机科学中的一个非常重要的组成部分就是图论算法,它们都可以追溯到欧拉和著名的哥尼斯堡七桥问题。许多优化问题都需要确定有关图推理的高效方法。
This problem involves determining a route for a postal worker so that all mail is delivered while the postal worker walks a minimal distance, so as to rest weary legs.
该问题就是要为邮递员确定一条路线,让他在投递所有信件的同时只走最短的距离,以节省脚力。
The Problem
问题
Given a sequence of streets (connecting given intersections) you are to write a program that determines the minimal cost tour that traverses every street at least once. The tour must begin and end at the same intersection.
给定一系列的街道(由路口相互联通),你要写一个程序来确定跑遍所有这些街道所需的最小旅程。旅程结束时必须回到起点的路口。
The "real-life" analogy concerns a postal worker who parks a truck at an intersection and then walks all streets on the postal delivery route (delivering mail) and returns to the truck to continue with the next route.
在现实生活中的情况是一个邮递员将它的卡车停在路口,然后跑遍所有的小巷来完成投递任务(发邮件),最后回到卡车继续下一次投递任务。
The cost of traversing a street is a function of the length of the street (there is a cost associated with delivering mail to houses and with walking even if no delivery occurs).
穿过一条街所花费的开销是这条街长度的函数(将邮件投递入户会产生开销,行走而没有投递也会产生开销)。
In this problem the number of streets that meet at a given intersection is called the degree of the intersection. There will be at most two intersections with odd degree. All other intersections will have even degree, i.e., an even number of streets meeting at that intersection.
在这个问题中,与一个路口相连的所有街道的数量称为这个路口的“度”。最多存在两个奇数度的路口,其它所有路口的度均为偶数,即有偶数条街道与该路口相连。
The Input
输入
The input consists of a sequence of one or more postal routes. A route is composed of a sequence of street names (strings), one per line, and is terminated by the string "deadend" which is NOT part of the route. The first and last letters of each street name specify the two intersections for that street, the length of the street name indicates the cost of traversing the street. All street names will consist of lowercase alphabetic characters.
输入由一条或多条邮递路线构成。路线由一系列的街道名称(字符串)来表示,每条街独占一行。字符串“deadend”表示一条路线的结束,但它本身不是路线的一部分。每条街道的第一个和最后一个字母代表了这条街道的两个路口,街道名称的长度即为穿过这条街道所需的开销。所有街道的名称都由小写字母构成。
For example, the name foo indicates a street with intersections f and o of length 3, and the name computer indicates a street with intersections c and r of length 8. No street name will have the same first and last letter and there will be at most one street directly connecting any two intersections. As specified, the number of intersections with odd degree in a postal route will be at most two. In each postal route there will be a path between all intersections, i.e., the intersections are connected.
比如说,一条街道的名称为“foo”表示它的两个路口为“f”和“o”,且长度为3;另一条街道的名称为“computer”表示它的两个路口为“c”和“r”,长度为8。不存在首尾字母相同的街道,两个路口最多由一条街道直接连通。在每条邮递路线中都存在一条路径可以跑遍所有的路口,即所有路口都是连通的。
The Output
输出
For each postal route the output should consist of the cost of the minimal tour that visits all streets at least once. The minimal tour costs should be output in the order corresponding to the input postal routes.
对应输入的每条邮递路线,应输出访问其所有街道至少一次的最短路径的开销。最短路径开销应按照输入的邮递路线的相应顺序输出。
Sample Input
输入示例
one
two
three
deadend
mit
dartmouth
linkoping
tasmania
york
emory
cornell
duke
kaunas
hildesheim
concord
arkansas
williams
glasgow
deadend
Sample Output
输出示例
11
114
Analysis
分析
在介绍算法前必须先明确几个概念。按题目的定义,可将十字路口视为节点,街道视为节点之间的边,那么所给出的数据就是一个强连通无向图(即任何边都可正反两个方向通过,且不存在无法到达的节点)。此外题目中还给出奇数度的路口数最多为2,这就满足了欧拉在近300百年前就证明了的一笔画问题(维基百科)的充要条件:奇顶点的数目等于0或2。
等等,题目并没有说奇顶点的数目不能等于1呀?事实上,那是不可能的,因为奇顶点的数目一定是偶数!推理证明如下:最简单的图有n个点,点与点之间没有边,那么所有点的度都为0。如果在这个图中增加一条线段,那么一定会为两个端点各增加1度,所以任何图的度数和都为偶数。假设图中有奇数个奇顶点,那么整个图的度数之和为奇数×奇数=奇数,与前面的推论矛盾,因此一个图中只可能存在偶数个奇顶点。
即然题目所给的图是可以用一笔划来画出的,那么所有街道只走一次就可以走完所有街道。根据欧拉回路的充要条件(证明比较繁琐,恕不赘述)可知,如果图中只有偶顶点,那么必然存在一条从起点出发,走完全部街道后返回原点的路线。如果存在奇顶点,那么要一遍走完所有街道则必须从其中一个奇顶点出发,到另一个奇顶点结束。这样题目的思路就很清晰了。为所有的这一切,感谢欧拉!(维基百科)
先将所有街道的长度和作为最短路径长度,如果存在奇顶点路口,还要加上两个奇顶点之间的最短路径。这道题的规模很小,最多26个路口,因此使用复杂度为O(n3)的弗洛伊德算法(维基百科)来解决最短路径问题是非常便捷的。
Solution
解答
#include <algorithm> #include <iostream> #include <string> using namespace std; //主函数 int main(void) { //aMat为连通路口的邻接矩阵,aIdHash为路口字母与其编号的对应表 //nNodeCnt为路口数量,nRout为最小路径长度 int aMat[26][26], aIdHash[26], nNodeCnt = 0, nRout = 0, nInf = 0xFFFFFF; fill(&aMat[0][0], &aMat[26][0], nInf); fill(&aIdHash[0], &aIdHash[26], nInf); //循环处理每一行输入,结束时将nRout清0 for (string strName; cin >> strName;) { //如果街名为结束符,则按前一阶段的输入数据进行运算 if (strName == "deadend") { //aOdd数组用于记录奇顶点,pOdd是aOdd的指针形式 int aOdd[2] = {0, 0}, *pOdd = &aOdd[0]; for (int i = 0; i < nNodeCnt && pOdd != &aOdd[2]; ++i) { //统计每一个顶点的相邻顶点数(连通路口数) for (int j = 0; j < nNodeCnt; *pOdd += (aMat[i][j++] != nInf)); //如果发现奇顶点,加入奇顶点数组 if (*pOdd % 2 != 0) { *(pOdd++) = i; } } //如果存在两个奇顶点,则开始Floyd算法,找最短路径 if (pOdd != aOdd) { for (int k = 0, nSum; k < nNodeCnt; ++k) { for (int i = 0; i < nNodeCnt; ++i) { //为加快运算减少代码,设置临时变量p = aMat[i] for (int j = 0, *p = &aMat[i][0]; j < nNodeCnt; ++j) { nSum = p[k] + aMat[k][j]; p[j] = nSum < p[j] ? nSum : p[j]; } } } nRout += aMat[aOdd[0]][aOdd[1]]; } //输出最小路径长度 cout << nRout << endl; //初始化数据,以便进行下一轮计算 fill(&aMat[0][0], &aMat[nNodeCnt][0], nInf); fill(&aIdHash[0], &aIdHash[26], nInf); nRout = nNodeCnt = 0; continue; } //街道名称的首尾字母 int nF = *strName.begin() - 'a', nL = *(strName.end() - 1) - 'a'; //验证路口字母是否已经存在,否则添加路口 nF = (aIdHash[nF] = aIdHash[nF] == nInf ? nNodeCnt++ : aIdHash[nF]); nL = (aIdHash[nL] = aIdHash[nL] == nInf ? nNodeCnt++ : aIdHash[nL]); //在邻接距阵中添加路口节点 aMat[nF][nL] = aMat[nL][nF] = strName.length(); //统计路径长度 nRout += strName.length(); } return 0; }
作者:王雨濛;新浪微博:@吉祥村码农;来源:《程序控》博客 -- http://www.cnblogs.com/devymex/ 此文章版权归作者所有(有特别声明的除外),转载必须注明作者及来源。您不能用于商业目的也不能修改原文内容。 |