poj 1041 John's trip 无向图求欧拉回路模板

缘起

无向(连通)图求欧拉回路模板. poj 1041 John’s trip

【1】中讲了判定欧拉回路的存在性问题. 本题来解决欧拉回路的构造问题.

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
给你一个无向图,数据格式如(点x 点y 边Z),表示由x点和y点构成了Z边。现在要问你该图中是否存在欧拉回路,
如果存在,则输出按照边的号字典序最小的那条欧拉回路(输入按序走过的所有边标号)。且题目中保证了该无向图是连通的。

【输入】
多样例. 每个样例包含多行,每行都是三个整数,x(x>0),y(y>0),z. x和y是点,z是边
每个样例的最后是x=0,y=0. 最后一个样例是x=0,y=0 不需要处理

【输出】
字典序最小的欧拉回路, 不存在则按样例输出

【样例输入】
1 2 1
2 3 2
3 1 6
1 2 5
2 3 3
3 1 4
0 0
1 2 1
2 3 2
1 3 3
2 4 4
0 0
0 0

【样例输出】
1 2 3 5 4 6
Round trip does not exist.

【限制】
1s

【是否接受special judge】

本题算是经典问题了. 解法是 dfs消圈

一个图G如果存在圈, 将此圈去掉之后如果还有欧拉回路的话, 那么G也存在欧拉回路. 其实这个性质是不难理解的, 因为欧拉路径其实就是由一条可能起点终点不同(2个奇度点)的边不重复的路径与若干个圈构成的,去掉圈相当于是化繁为简.

所以我们可以不断地使用dfs消圈(具体表现为开一个数组对所有的边进行哈希,一旦走过了,则不让再走了. 则其实在dfs中走不下去了其实就是找到了一个圈)

上图中的小圈就是在dfs过程中找到了的一个圈. 一旦找到了一个圈,则将其消去——其实代码中不必真的消去,而是通过哈希了边,让边不能再次被走过(欧拉回路不正是这样吗?). 然后找到圈之后, 再将进入圈之前的边进栈.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//#include "stdafx.h"

#include <stdio.h>
#include <vector>
#include <algorithm>
using namespace std;
#include <string.h>
//#define LOCAL
typedef pair<int, int> P; // first是点的编号,second是边的编号
typedef vector<P>::iterator vit;

int john,deg[50],top, ans[2000]; // 欧拉回路的出发点, top是ans中的元素的数量
vector<P> g[50]; // 图
bool v[2000]; // 不超过1995条边, 不超过44个顶点

bool cmp(P &a, P &b)
{
return a.second<b.second;
}

void dfs(int i) // i是当前节点
{
for (vit x = g[i].begin(); x!=g[i].end(); x++)
{
if (!v[x->second])
{
v[x->second] = true;
dfs(x->first); // 从此dfs退出则找到了一个圈(这个圈上所有的边都哈希成了1,表示不可以再走了,即达到了消圈的目的)
ans[top++] = x->second; // 消完圈表示从这个圈退出来了,则可以将进入此圈的边入栈
}
}
}

bool xxx()
{
for (int i = 1; i<50; i++)
{
if (deg[i] &1)
{
return false; // 不存在欧拉回路
}
}
dfs(john); // 存在欧拉回路就开始使用dfs收集字典序最小的欧拉回路
return true;
}

int main()
{
#ifdef LOCAL
freopen("d:\\data.in", "r", stdin);
//freopen("d:\\my.out", "w", stdout);
#endif
int x,y,z;
while(scanf("%d%d", &x, &y), x||y)
{
for (int i = 1; i<50; i++)
{
g[i].clear();
}
memset(deg, 0, sizeof(deg));
scanf("%d", &z);
john = min(x,y); // 根据题意找到出发点
deg[x]++, deg[y]++;
g[x].push_back(P(y,z)); // 表示x和y通过边z进行连接
g[y].push_back(P(x,z));// 无向图
while(scanf("%d%d", &x, &y), x||y)
{
scanf("%d", &z);
deg[x]++,deg[y]++;
g[x].push_back(P(y,z));
g[y].push_back(P(x,z));
}
for (int i = 1; i<50; i++)
{
sort(g[i].begin(), g[i].end(), cmp); // 每个顶点的边按照边的编号进行升序排序. 这是为了能优先dfs编号较小的边,因为题目要求字典序最小的欧拉环路
}
memset(v, 0, sizeof(v));
if (xxx())
{
while(top) // 逆序输出, 你看看图就知道了
{
printf("%d", ans[--top]);
if (top)
{
putchar(' ');
}
}
puts("");
}
else
{
puts("Round trip does not exist.");
}
}
return 0;
}

注意, 28和29行是不能交换顺序的. 反例是

1
2
3
4
5
6
7
8
9
1 2 1
2 3 2
1 3 3
2 4 4
2 4 5
0 0
0 0
答案是 1 2 4 5 3
交换后是 1 2 3 4 5 这样的错误答案

注意,本文求无向图的欧拉环路(路径)的算法叫做 fleury 算法, 该算法的原理会进一步在【2】中讲解.

ac情况

Status Accepted
Time 47ms
Memory 260kB
Length 1738
Lang C++
Submitted 2019-09-06 13:30:24
Shared
RemoteRunId 20837790

参考

【1】https://yfsyfs.github.io/2019/09/06/hdu-1878-%E6%AC%A7%E6%8B%89%E5%9B%9E%E8%B7%AF-%E5%88%A4%E6%96%AD%E6%97%A0%E5%90%91%E5%9B%BE%E6%98%AF%E5%90%A6%E5%AD%98%E5%9C%A8%E6%AC%A7%E6%8B%89%E5%9B%9E%E8%B7%AF/

【2】https://yfsyfs.github.io/2019/09/06/hihocoder-1181-%E6%AC%A7%E6%8B%89%E8%B7%AF%C2%B7%E4%BA%8C-Fleury%E7%AE%97%E6%B3%95/