欧拉图,欧拉回路学习笔记
一笔画问题
在哥尼斯堡市区,普雷戈利亚河上有 \(7\) 座桥把河中心小岛和河岸连接起来。
当时有人提出了一个问题:一个行者怎样才能不重复、不遗漏地一次走完七座桥?
当时巨佬欧拉证明了这个问题,所以一笔画问题也称为欧拉路。
欧拉路&欧拉回路
定义
欧拉路是经过图中每条边恰好一次的路径,欧拉回路是经过图中每条边恰好一次的回路。
我们大概理解一下,如果一个图存在一笔画,则一笔画的路径叫做欧拉路,如果最后又回到起点,那这个路径叫做欧拉回路。
所以,欧拉回路也是欧拉路。
判定方法
这里要分有向图和无向图来讨论。
无向图
- 图存在欧拉回路当且仅当满足度非 \(0\) 的节点互相可达且没有奇数度数的节点。
- 图存在欧拉路当且仅当满足度非 \(0\) 的节点互相可达且奇数度数的节点只有 \(0\) 个或者 \(2\) 个。
可能会有疑惑,为什么是度非 \(0\) 的节点互相可达呢而不是全部都连通呢?
因为欧拉路是边的集合,如果只有孤立的点,那么也是没有关系的。
说明一下,如果存在欧拉路,那么起点一定是在某一个奇数度数的点上,终点也是。
所以,如果没有奇数度数的点,起点和终点相连,就变成了欧拉回路。
有向图
- 图存在欧拉回路当且仅当满足度非 \(0\) 的节点是强连通的且出度全部等于入度。
- 图存在欧拉路当且仅当满足有向边给成无向边后,度非 \(0\) 的节点互相可达且只有两个节点入度和出度相差 \(1\)。
说明一下,如果存在欧拉路,那么起点一定是在某一个奇数度数的点上,终点也是。
所以,如果没有奇数度数的点,起点和终点相连,就变成了欧拉回路。
求欧拉路&欧拉回路的方法
求欧拉路&欧拉回路是用 DFS 来求的。
在回溯时去记录编号,最后加入起点,逆序输出。
为什么呢?如果存在欧拉路&欧拉回路,就一定是搜索出来的一条路径,回溯就能找到实际的路径,而搜索时记录会导致答案出现错误。
别忘了加入起点。
实现
有向图
为什么要分有向图和无向图呢?
我们想想为什么 DFS 图是 \(O(n+m)\) 的呢?
因为每个点会只访问 \(1\) 次。
但是欧拉路&欧拉回路会访问每个点多次,时间不就爆了吗?
怎么办呢?我们可以用一个小技巧,记录一个 \(f_x\),表示访问到了 \(x\) 节点的哪一个,这样子就不会重复枚举了,因为 \(f_x\) 是永远不回退的。
时间复杂度:\(O(n+m)\)。
问一下,为什么不是 \(O(m)\) 的呢?
因为还要判定欧拉路&欧拉回路啊!
代码(欧拉回路改改判定就行了):
void dfs(int u) {
while (f[u] < v[u]) {
int v = adj[u][f[u]];
++f[u];
dfs(v);
c[++l] = v;
}
}
void Euler() {
int s = 0, x = 0, y = 0;
for (int i = 1; i <= n; i++) {
if (ind[i] + 1 == outd[i]) ++x, s = i;
if (ind[i] != outd[i]) ++y;
}
if (!(y == 0 || (x == 1 && y == 2))) {
puts("No");
return;
}
if (!s) for (int i = 1; i <= n; i++) {
if (ind[i]) {
s = i;
break;
}
}
memset(f, 0, sizeof(f));
dfs(s);
c[++l] = s;
if (l != n + 1) {
puts("No");
} else {
puts("Yes");
}
for (int i = l; i >= 1; i--) printf("%d", c[i]);
puts("");
}
无向图
其他和有向图一样,但是双向的该怎么办呢?
我们又来一个小技巧。我们发现 \(x \oplus 1\) 总是成对出现的,所以我们可以建一个编号来做这个事。
代码:
void dfs(int u) {
while (f[u] < v[u]) {
int v = adj[u][f[u]].first, idx = adj[u][f[u]].second;
if (!b[idx]) {
b[idx] = b[idx ^ 1] = true;
++f[u];
dfs(v);
c[++l] = v;
} else {
++f[u];
}
}
}
void Euler() {
int s = 0;
for (int i = 1; i <= n; i++) {
if (d[i] & 1) s = i;
}
if (!s) for (int i = 1; i <= n; i++) {
if (d[i]) {
s = i;
break;
}
}
l = 0;
memset(b, false, sizeof(b));
memset(f, 0, sizeof(f));
dfs(s);
c[++l] = s;
for (int i = l; i >= 1; i--) printf("%d ", c[i]);
puts("");
}
例题
P7771 【模板】欧拉路径
怎么让字典序更小呢?
因为在前面的肯定是先进入的,要想先进入的数更小,显然先排序。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define all(x) x.begin(), x.end()
#define inf (1 << 30)
#define lnf (1LL << 60)
typedef pair<int, int> PII;
constexpr int N = 100000 + 7;
constexpr int P = 998244353;
int n, m;
vector<int> adj[N];
int f[N], v[N], ind[N], outd[N], c[N], l;
void dfs(int u) {
while (f[u] < v[u]) {
int v = adj[u][f[u]];
++f[u];
dfs(v);
c[++l] = v;
}
}
void Euler() {
int s = 0, x = 0, y = 0;
for (int i = 1; i <= n; i++) {
if (ind[i] + 1 == outd[i]) ++x, s = i;
if (ind[i] != outd[i]) ++y;
}
if (!(y == 0 || (x == 1 && y == 2))) {
puts("No");
return;
}
if (!s) for (int i = 1; i <= n; i++) {
if (ind[i]) {
s = i;
break;
}
}
l = 0;
memset(f, 0, sizeof(f));
dfs(s);
c[++l] = s;
if (l != m + 1) {
puts("No");
} else {
for (int i = l; i >= 1; i--) printf("%d ", c[i]);
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int u, v;
scanf("%d%d", &u, &v);
++outd[u], ++ind[v];
adj[u].push_back(v);
}
for (int i = 1; i <= n; i++) {
v[i] = adj[i].size();
sort(all(adj[i]));
}
Euler();
return 0;
}
其实,考到的次数也不多。

浙公网安备 33010602011771号