分析
在介绍算法前必须先明确几个概念。按题目的定义,可将十字路口视为节点,街道视为节点之间的边,那么所给出的数据就是一个强连通无向图(即任何边都可正反两个方向通过,且不存在无法到达的节点)。此外题目中还给出奇数度的路口数最多为 222,这就满足了欧拉在近 300300300 百年前就证明了的一笔画问题的充要条件:奇顶点的数目等于 000 或 222。
等等,题目并没有说奇顶点的数目不能等于 111 呀?事实上,那是不可能的,因为奇顶点的数目一定是偶数!推理证明如下:最简单的图有n个点,点与点之间没有边,那么所有点的度都为 000。如果在这个图中增加一条线段,那么一定会为两个端点各增加 111 度,所以任何图的度数和都为偶数。假设图中有奇数个奇顶点,那么整个图的度数之和为奇数 ×\times× 奇数 === 奇数,与前面的推论矛盾,因此一个图中只可能存在偶数个奇顶点。
即然题目所给的图是可以用一笔划来画出的,那么所有街道只走一次就可以走完所有街道。根据欧拉回路的充要条件(证明比较繁琐,恕不赘述)可知,如果图中只有偶顶点,那么必然存在一条从起点出发,走完全部街道后返回原点的路线。如果存在奇顶点,那么要一遍走完所有街道则必须从其中一个奇顶点出发,到另一个奇顶点结束。这样题目的思路就很清晰了。
先将所有街道的长度和作为最短路径长度,如果存在奇顶点路口,还要加上两个奇顶点之间的最短路径。这道题的规模很小,最多 262626 个路口,因此使用复杂度为 O(n3)O(n^3)O(n3) 的 弗洛伊德算法 来解决最短路径问题是非常便捷的。
代码实现
#include <algorithm>
#include <iostream>
#include <string>
using namespace std;
int main() {
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);
for (string strName; cin >> strName;) {
if (strName == "deadend") {
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;
}
}
if (pOdd != aOdd) {
for (int k = 0, nSum; k < nNodeCnt; ++k) {
for (int i = 0; i < nNodeCnt; ++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;
}
文章介绍了如何利用欧拉回路理论解决给定强连通无向图的最短路径问题,强调奇数度节点对路径的影响,并提供了使用弗洛伊德算法的代码实例。
7239

被折叠的 条评论
为什么被折叠?



