1. 问题描述:
农民John每年有很多栅栏要修理。他总是骑着马穿过每一个栅栏并修复它破损的地方。John是一个与其他农民一样懒的人。他讨厌骑马,因此从来不两次经过一个栅栏。你必须编一个程序,读入栅栏网络的描述,并计算出一条修栅栏的路径,使每个栅栏都恰好被经过一次。John能从任何一个顶点(即两个栅栏的交点)开始骑马,在任意一个顶点结束。每一个栅栏连接两个顶点,顶点用 1 到 500 标号(虽然有的农场并没有 500 个顶点)。一个顶点上可连接任意多( ≥1 )个栅栏。所有栅栏都是连通的(也就是你可以从任意一个栅栏到达另外的所有栅栏)。你的程序必须输出骑马的路径(用路上依次经过的顶点号码表示)。我们如果把输出的路径看成是一个500进制的数,那么当存在多组解的情况下,输出500进制表示法中最小的一个 (也就是输出第一个数较小的,如果还有多组解,输出第二个数较小的,等等)。
输入数据保证至少有一个解。
输入格式
第 1 行:一个整数 F,表示栅栏的数目;第 2 到 F+1 行:每行两个整数 i,j 表示这条栅栏连接 i 与 j 号顶点。
输出格式
输出应当有 F+1 行,每行一个整数,依次表示路径经过的顶点号。注意数据可能有多组解,但是只有上面题目要求的那一组解是认为正确的。
数据范围
1 ≤ F ≤ 1024,
1 ≤ i,j ≤ 500
输入样例:
9
1 2
2 3
3 4
4 2
4 5
2 5
5 6
5 7
4 6
输出样例:
1
2
3
4
2
5
4
6
5
7
来源:https://www.acwing.com/problem/content/description/1126/
2. 思路分析:
分析题目可以知道我们可以将每一个栅栏中的两个点看成是图中的两个点,栅栏之间的路径看成是图中的一条边,我们需要从图中的某一个节点出发,并且从任意一个节点结束,每一条路径只能够经过一次,所以属于经典的欧拉路径(欧拉回路)的问题,而且这道题目要求输出字典序最小的答案,所以我们需要考虑怎么样遍历才可以记录下节点编号字典序最小的欧拉路径(欧拉回路),欧拉路径问题其实有一个比较特殊的一个地方对于每一个中间点来说只要有路径走出去那么就一定有路径走回来,所以对于当前的节点u是最后遍历完当前所有的邻接点之后路径中才加入的当前节点的编号,所以对于当前的节点u我们可以按照字典序从小到大遍历对应的邻接点,这样当遍历完所有邻接点之后加入当前的节点u,得到的字典序一定是最小的,因为这道题目数据规模不是特别大所以可以使用邻接矩阵来存储,邻接矩阵存储的一个好处是可以按照节点编号顺序从小到大遍历当前节点的邻接点,这样就不用对邻接点进行排序,而且在遍历的时候不用标记对应的边是否已经访问,直接对应位置的边数减1即可(如果使用邻接表来存储则需要标记边是否已经访问过了)。
3. 代码如下:
from typing import List
class Solution:
n = 500
res = None
def dfs(self, u: int, g: List[List[int]]):
for i in range(1, self.n + 1):
if g[u][i] > 0:
# 相当于标记表已经访问过了
g[u][i] -= 1
g[i][u] -= 1
self.dfs(i, g)
# 加入欧拉路径的点
self.res.append(u)
def process(self):
n = int(input())
g = [[0] * 510 for i in range(510)]
d = [0] * 510
for i in range(n):
a, b = map(int, input().split())
g[a][b] += 1
g[b][a] += 1
# a的度数加1
d[a] += 1
# b的度数加1
d[b] += 1
start = 1
# 因为起点并不一定是从1开始的而且可能是欧拉回路所以需要使用下面这个循环来找到度数大于0的点
while d[start] == 0: start += 1
self.res = list()
for i in range(1, self.n + 1):
# 找到第一个奇数顶点
if d[i] % 2:
start = i
break
self.dfs(start, g)
# 逆序输出答案
for i in range(len(self.res) - 1, -1, -1):
print(self.res[i])
if __name__ == "__main__":
Solution().process()