欧拉路径 & 欧拉回路
定义
-
欧拉路径:经过连通图中所有边恰好一次的迹称为 欧拉路径。
-
欧拉回路:经过连通图中所有边恰好一次的回路称为 欧拉回路。即首尾相连的欧拉路径。
-
欧拉图:具有欧拉回路的图称为 欧拉图。
-
半欧拉图:具有欧拉路径但不具有欧拉回路的图称为 半欧拉图。
不经过重复边的途径称为 迹。
判定
-
有向图欧拉路径:图中恰好存在 \(1\) 个点的出度比入度多 \(1\) (该点即为起点 \(S\)),\(1\) 个点的入度比出度多 \(1\) (该点即为终点 \(T\)),其余点的出度等于入度。
-
有向图欧拉回路:图中所有点的入度等于出度(起点 \(S\) 和终点 \(T\) 可以为任意点)。
-
无向图欧拉路径:图中恰好存在 \(2\) 个点的度数是奇数,其余点的度数为偶数,这两个度数为奇数的点即为欧拉路径的起点 \(S\) 和终点 \(T\) 。
-
无向图欧拉回路:图中所有点的度数都是偶数(起点 \(S\) 和终点 \(T\) 可以为任意点)。
注意:存在欧拉路径的充要条件是无向图连通,有向图弱连通。
对于有向图的两点 \(u, v\) ,若将有向边改为无向边后 \(u, v\) 连通,则称 \(u, v\) 弱连通。
构造
采用 Hierholzer 算法求解欧拉路径或欧拉回路。
具体地,遍历当前点 \(u\) 的所有出边 \(u \rightarrow v\)。若该边未走过,则向点 \(v\) DFS。遍历完所有出边后,将 \(u\) 加入回路。最终得到一条反向的欧拉回路,将其翻转即可。
代码实现上,有向弱连通图删边比标记边的 \(O(m^2)\) (其中 \(m\) 是边数)时间复杂度更优。可以用数组维护邻接矩阵,也可以用 vector 或链式前向星维护时间复杂度更优的邻接表。
以欧拉路径的模版题为例,要求有向弱连通图中字典序最小的欧拉路径。由于点数 \(n\) 和边数 \(m\) 均是 \(10^5\) 级别的数据,领接矩阵的 \(O(n^2)\) 的时间复杂度不可接受。考虑采用类似链表的方式记录数组 \(nxt\),其中 \(nxt_u\) 表示 \(u\) 的邻接表中下一个访问的边的编号。初始时 \(nxt_u = 0\) 即可。考虑通过排序贪心地将点的编号越小的点排在最前面,最终求得的一定是字典序最小的欧拉路径。以下代码的时间复杂度为 \(O(n + m \log m)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 8;
int n, m, in[N], out[N], nxt[N];
vector<int> e[N];
stack<int> stk;
void dfs(int u) {
for (int i = nxt[u]; i < e[u].size(); i = nxt[u]) {
nxt[u] = i + 1;
dfs(e[u][i]);
}
stk.push(u);
}
int main() {
cin >> n >> m;
for (int i = 1, u, v; i <= m; i++) {
cin >> u >> v;
e[u].push_back(v);
out[u]++; in[v]++;
}
for (int i = 1; i <= n; i++) sort(e[i].begin(), e[i].end());
int cir = 1, cnts = 0, cntt = 0, s = 1;
for (int i = 1; i <= n; i++)
if (in[i] != out[i]) {
cir = 0;
if (in[i] - out[i] == 1) cntt++;
else if (out[i] - in[i] == 1) s = i, cnts++;
else {
cout << "No";
return 0;
}
}
if (!cir && (cnts != 1 || cntt != 1)) {
cout << "No";
return 0;
}
dfs(s);
while (!stk.empty()) {
cout << stk.top() << " ";
stk.pop();
}
return 0;
}
例题
从每个字符的第一个字符向最后一个字符连边,以每条边对应的字符串的字典序排序,跑有向图欧拉路径。时间复杂度为 \(O(S \log S)\),其中 \(S = \sum \lvert {s_i} \rvert\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 8;
int in[N >> 3], out[N >> 3], n, cnt;
bool flag, vis[N];
string a[N], now[N], ans[N];
void dfs(int last, int step) {
if (flag) return;
if (step == n) {
flag = true;
for (int i = 1; i <= cnt; i++) ans[i] = now[i];
return;
}
for (int i = 1; i <= n; i++) {
if (vis[i]) continue;
if (a[last][a[last].size() - 1] == a[i][0]) {
vis[i] = true;
now[++cnt] = a[i];
dfs(i, step + 1);
vis[i] = false;
--cnt;
}
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
out[a[i][0]]++;
in[a[i][a[i].size() - 1]]++;
}
sort(a + 1, a + n + 1);
char s, t;
for (char c = 'a'; c <= 'z'; c++) {
if (in[c] - out[c] == 1) t = c;
else if (out[c] - in[c] == 1) s = c;
}
int start = 1;
for (int i = 1; i <= n; i++)
if (a[i][0] == s && (a[i][a[i].size() - 1] != t || in[t] > 1)) {
start = i;
break;
}
now[++cnt] = a[start];
vis[start] = true;
dfs(start, 1);
if (!flag) {
cout << "***";
return 0;
}
for (int i = 1; i <= n; i++) {
cout << ans[i];
if (i != n) cout << '.';
}
return 0;
}
考虑单个连通块,若它不存在欧拉回路,则该连通块无解。考虑多个连通块,只要有一个连通块无解,则该图无解。若连通块不是孤点(即有黑边)贡献为 \(1\),否则为 \(0\)。
用并查集维护连通块黑边的数量和对答案的贡献。由上分析知,一个图有解等价于每个点的度数都是偶数。
启示:只需记录每个节点度数的奇偶性,维护度数之和,只要为 \(0\) 就有解。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 8;
struct Edge { int u, v, c; } e[N];
int fa[N], dg[N], cnt[N], n, m, q, now;
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
void merge(int x, int y) { fa[find(x)] = find(y); }
void update(int u, int v) {
now -= dg[u] + dg[v];
dg[u] ^= 1; dg[v] ^= 1;
now += dg[u] + dg[v];
}
int read() {
char ch = getchar(); int num = 0;
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') { num = (num << 1) + (num << 3) + ch - '0'; ch = getchar(); }
return num;
}
int main() {
n = read(); m = read();
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1, u, v, c; i <= m; i++) {
u = read(), v = read(), c = read();
e[i] = {u, v, c};
if (c == 1) update(u, v);
merge(u, v);
}
int ans = 0;
for (int i = 1; i <= m; i++)
if (e[i].c == 1) {
int f = find(e[i].u);
if (++cnt[f] == 1) ans++;
}
q = read();
while (q--) {
int op; op = read();
if (op == 1) {
int x; x = read();
x++;
update(e[x].u, e[x].v);
int f = find(e[x].u);
if (e[x].c == 1) {
e[x].c = 0;
if (--cnt[f] == 0) ans--;
} else {
e[x].c = 1;
if (++cnt[f] == 1) ans++;
}
} else cout << (now == 0 ? ans : -1) << "\n";
}
return 0;
}

浙公网安备 33010602011771号