欧拉图、哈密尔顿图
p.s. 二者的必要条件是图为连通图
欧拉图
定义
欧拉通路:能一次性走完一张图上所有的边,且每条边只经过一次
欧拉回路:能一次性走完一张图上所有的边,每条边只经过一次,且这条路径构成一个回路(即最终回到了出发点)
有欧拉回路的图成为欧拉图(Eulerian),有欧拉通路但无回路的称为半欧拉图(semi-Eulerian)
欧拉回路必须满足的条件:
无向图:度数为奇数的点的个数 == 0 或者 == 2(== 0 : 欧拉回路, == 2 :欧拉通路)
有向图:除了起点和终点,每个点的入度 == 出度,满足 起点的出度-入度 == 1 && 终点入度-出度 == 1 或者 起点终点为同一个
思路
dfs。
首先需要确定dfs的起点,这可以通过输入边时统计度数实现,如果奇数度数的点不是0或2,则无欧拉通路;有两个奇数点的,找到其中一个点作为起点;度数全为偶数的,任意指定一个点作为起点。
然后开始dfs,每经过一条路时就把它的次数--,免得重复经过,当无路可走时,就存下当前的点(注意是逆序),回溯,继续遍历
模版题1
https://www.luogu.com.cn/problem/P2731
这题不仅要先判断,还要输出字典序最小的遍历序列,这里有坑。
关于为什么不能正序输出但可以倒序:
首先,在无向图中,如果有一条欧拉路,起点为 \(v\) ,用一个点 \(u\) 向已知起点 \(v\) 再连一条边,那么现在就有了从 \(u\) 出发的一条欧拉路。
倒序输出相当于,之前先找到了以 \(v\) 为起点的一条欧拉路,即先解决子问题,现在在它前面加入\(u\)
正序输出相当于,给你一个大问题,你没有解决子问题,但是先规定先从当前点出发,不能保证从当前出发能找到欧拉路的正确性
code
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
using namespace std;
const int N = 500 + 10;
int m, n = 500, u, v;
int rd[N], path[N][N];
int start = 1;
int cnt, ans[N];
void dfs(int u) {
for(int i = 1; i <= 500; i++) {
if(path[u][i]) {
path[u][i]--;
path[i][u]--; // 有向图删掉
dfs(i);
}
}
ans[++cnt] = u;
}
int main() {
ios::sync_with_stdio(false);
cin >> m;
for(int i = 1; i <= m; i++) {
cin >> u >> v;
path[u][v]++; path[v][u]++;
rd[u]++; rd[v]++;
}
for(int i = 1; i <= 500; i++) {
if(rd[i] % 2) {
start = i;
break;
}
}
dfs(start);
for(int i = cnt; i >= 1; i--) cout << ans[i] << endl;
return 0;
}
模版题2
https://www.luogu.com.cn/problem/P1341
感觉建图挺有意思的,可以自己先思考。
一对点对相当于给这两个字母连了一条无向边。
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
using namespace std;
const int N = 500 + 10;
int m, n = 500;
string s;
int rd[N], path[N][N];
int start;
int cnt, ans[N];
inline int chg(char ch) {
return (int)(ch);
}
void dfs(int u) {
for(int i = 1; i <= 200; i++) {
if(i >= 'a' && i <= 'z' || i >= 'A' && i <= 'Z') {
if(path[u][i]) {
path[u][i]--;
path[i][u]--; // 有向图删掉
dfs(i);
}
}
}
ans[++cnt] = u;
}
int main() {
ios::sync_with_stdio(false);
cin >> m;
for(int i = 1, u, v; i <= m; i++) {
cin >> s;
u = chg(s[0]); v = chg(s[1]);
path[u][v]++; path[v][u]++;
rd[u]++; rd[v]++;
}
int odd = 0;
for(int i = 1; i <= 200; i++) {
if(i >= 'a' && i <= 'z' || i >= 'A' && i <= 'Z') {
if(rd[i] % 2) {
odd++;
}
}
}
if(odd != 0 && odd != 2) {
cout << "No Solution\n";
return 0;
}
if(odd == 0) {
for(int i = 1; i <= 200; i++) {
if(rd[i]) {
start = i;
break;
}
}
}
else {
for(int i = 1; i <= 200; i++) {
if(rd[i] % 2) {
start = i;
break;
}
}
}
dfs(start);
// cout << "cnt = " << cnt << endl; ///
for(int i = cnt; i >= 1; i--) cout << (char)ans[i];
cout << '\n';
return 0;
}
哈密尔顿图
定义
哈密尔顿路:经过一张图上所有的点的通路
哈密尔顿回路:经过一张图上所有的点的通回路
有哈密尔顿回路的图成为哈密尔顿图(Hamilton),有哈密尔顿通路但无回路的称为半哈密尔顿图(semi-Hamilton)